今回は、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 と順にデータが送信される。