WebRTC(RTCPeerConnection)

今回は、RTCPeerConnectionについてです。

(1) Basic peer connection demo between two tabs

同一ブラウザー内での PeerConnectionの例は前回行っています。今回はブラウザーを2つ上げてその両者間でPeerConnectionする例です。

index.html

画面の設定をしています。

main.js

StartボタンをクリックするとJavaが実行されます。今回のキーは、BroadcastChannel(‘webrtc’);です。これをシグナリングに使用しています。この関数に関してMDNに詳細が有ります。同一グループ内にメッセージを一斉に送る関数。引数がグループへの登録となるようです。

main.js

/*
 *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
 */

'use strict';

const startButton = document.getElementById('startButton');
const hangupButton = document.getElementById('hangupButton');
hangupButton.disabled = true;

const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');

let pc;
let localStream;

const signaling = new BroadcastChannel('webrtc');
signaling.onmessage = e => {
  if (!localStream) {
    console.log('not ready yet');
    return;
  }
  switch (e.data.type) {
    case 'offer':
      handleOffer(e.data);
      break;
    case 'answer':
      handleAnswer(e.data);
      break;
    case 'candidate':
      handleCandidate(e.data);
      break;
    case 'ready':
      // A second tab joined. This tab will initiate a call unless in a call already.
      if (pc) {
        console.log('already in call, ignoring');
        return;
      }
      makeCall();
      break;
    case 'bye':
      if (pc) {
        hangup();
      }
      break;
    default:
      console.log('unhandled', e);
      break;
  }
};

startButton.onclick = async () => {
  localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
  localVideo.srcObject = localStream;


  startButton.disabled = true;
  hangupButton.disabled = false;

  signaling.postMessage({type: 'ready'});
};

hangupButton.onclick = async () => {
  hangup();
  signaling.postMessage({type: 'bye'});
};

async function hangup() {
  if (pc) {
    pc.close();
    pc = null;
  }
  localStream.getTracks().forEach(track => track.stop());
  localStream = null;
  startButton.disabled = false;
  hangupButton.disabled = true;
};

function createPeerConnection() {
  pc = new RTCPeerConnection();
  pc.onicecandidate = e => {
    const message = {
      type: 'candidate',
      candidate: null,
    };
    if (e.candidate) {
      message.candidate = e.candidate.candidate;
      message.sdpMid = e.candidate.sdpMid;
      message.sdpMLineIndex = e.candidate.sdpMLineIndex;
    }
    signaling.postMessage(message);
  };
  pc.ontrack = e => remoteVideo.srcObject = e.streams[0];
  localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
}

async function makeCall() {
  await createPeerConnection();

  const offer = await pc.createOffer();
  signaling.postMessage({type: 'offer', sdp: offer.sdp});
  await pc.setLocalDescription(offer);
}

async function handleOffer(offer) {
  if (pc) {
    console.error('existing peerconnection');
    return;
  }
  await createPeerConnection();
  await pc.setRemoteDescription(offer);

  const answer = await pc.createAnswer();
  signaling.postMessage({type: 'answer', sdp: answer.sdp});
  await pc.setLocalDescription(answer);
}

async function handleAnswer(answer) {
  if (!pc) {
    console.error('no peerconnection');
    return;
  }
  await pc.setRemoteDescription(answer);
}

async function handleCandidate(candidate) {
  if (!pc) {
    console.error('no peerconnection');
    return;
  }
  if (!candidate.candidate) {
    await pc.addIceCandidate(null);
  } else {
    await pc.addIceCandidate(candidate);
  }
}

2つのブラウザを上げ、

  • 21行のconst signaling = new BroadcastChannel(‘webrtc’);が実行
  • signaling という BroadcastChannelが両者ブラウザ内で作成されます。
  • 次に一方のブラウザでStartボタンをクリックすると、56行 startButton.onclick = async ()が実行されます。
  • 57行 localStreamが宣言。(メディア使用の許可を取る)
  • 64行 signaling.postMessage({type: ‘ready’}); signalingにtyppe ”ready”でデータを送信
  • メッセージの送信で発生するイベントに対し相手側ブラウザが”ready”で対応する。
  • この時点で相手側ブラウザは、localStreamが宣言されていないので、23行 if (!localStream) {により何もせずに戻る
  • 次に相手側ブラウザがStartボタンをクリックすると元ブラウザと同じ処理が行われる。
  • 相手ブラウザがsignalingにtyppe ”ready”でデータを元ブラウザに送信すると、23行のメッセージハンドラが起動する
  • 元のブラウザでは既にlocalStreamが宣言されているので、メセージtype”ready”の操作が行われる。
  • これ以降は、Offer, Answer, Candidate と順にデータが送信される。