mirror of
https://github.com/mii443/libdatachannel.git
synced 2025-08-22 15:15:28 +00:00
195 lines
5.3 KiB
JavaScript
195 lines
5.3 KiB
JavaScript
/*
|
|
* libdatachannel example web client
|
|
* Copyright (C) 2020 Lara Mackey
|
|
* Copyright (C) 2020 Paul-Louis Ageneau
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
window.addEventListener('load', () => {
|
|
|
|
const config = {
|
|
iceServers: [{
|
|
urls: 'stun:stun.l.google.com:19302', // change to your STUN server
|
|
}],
|
|
};
|
|
|
|
const localId = randomId(8);
|
|
|
|
const url = `ws://localhost:8000/${localId}`;
|
|
|
|
const peerConnectionMap = {};
|
|
const dataChannelMap = {};
|
|
|
|
const offerId = document.getElementById('offerId');
|
|
const offerBtn = document.getElementById('offerBtn');
|
|
const sendMsg = document.getElementById('sendMsg');
|
|
const sendBtn = document.getElementById('sendBtn');
|
|
const _localId = document.getElementById('localId');
|
|
_localId.textContent = localId;
|
|
|
|
console.log('Connecting to signaling...');
|
|
openSignaling(url)
|
|
.then((ws) => {
|
|
console.log('WebSocket connected, signaling ready');
|
|
offerId.disabled = false;
|
|
offerBtn.disabled = false;
|
|
offerBtn.onclick = () => offerPeerConnection(ws, offerId.value);
|
|
})
|
|
.catch((err) => console.error(err));
|
|
|
|
|
|
function openSignaling(url) {
|
|
return new Promise((resolve, reject) => {
|
|
const ws = new WebSocket(url);
|
|
ws.onopen = () => resolve(ws);
|
|
ws.onerror = () => reject(new Error('WebSocket error'));
|
|
ws.onclosed = () => console.error('WebSocket disconnected');
|
|
ws.onmessage = (e) => {
|
|
if(typeof(e.data) != 'string') return;
|
|
const message = JSON.parse(e.data);
|
|
const { id, type } = message;
|
|
|
|
let pc = peerConnectionMap[id];
|
|
if(!pc) {
|
|
if(type != 'offer') return;
|
|
|
|
// Create PeerConnection for answer
|
|
console.log(`Answering to ${id}`);
|
|
pc = createPeerConnection(ws, id);
|
|
}
|
|
|
|
switch(type) {
|
|
case 'offer':
|
|
case 'answer':
|
|
pc.setRemoteDescription({
|
|
sdp: message.description,
|
|
type: message.type,
|
|
})
|
|
.then(() => {
|
|
if(type == 'offer') {
|
|
// Send answer
|
|
sendLocalDescription(ws, id, pc, 'answer');
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'candidate':
|
|
pc.addIceCandidate({
|
|
candidate: message.candidate,
|
|
sdpMid: message.mid,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function offerPeerConnection(ws, id) {
|
|
// Create PeerConnection
|
|
console.log(`Offering to ${id}`);
|
|
pc = createPeerConnection(ws, id);
|
|
|
|
// Create DataChannel
|
|
const label = "test";
|
|
console.log(`Creating DataChannel with label "${label}"`);
|
|
const dc = pc.createDataChannel(label);
|
|
setupDataChannel(dc, id);
|
|
|
|
// Send offer
|
|
sendLocalDescription(ws, id, pc, 'offer');
|
|
}
|
|
|
|
// Create and setup a PeerConnection
|
|
function createPeerConnection(ws, id) {
|
|
const pc = new RTCPeerConnection(config);
|
|
pc.onconnectionstatechange = () => console.log(`Connection state: ${pc.connectionState}`);
|
|
pc.onicegatheringstatechange = () => console.log(`Gathering state: ${pc.iceGatheringState}`);
|
|
pc.onicecandidate = (e) => {
|
|
if (e.candidate) {
|
|
// Send candidate
|
|
sendLocalCandidate(ws, id, e.candidate);
|
|
}
|
|
};
|
|
pc.ondatachannel = (e) => {
|
|
const dc = e.channel;
|
|
console.log(`"DataChannel from ${id} received with label "${dc.label}"`);
|
|
setupDataChannel(dc, id);
|
|
|
|
dc.send(`Hello from ${localId}`);
|
|
|
|
sendMsg.disabled = false;
|
|
sendBtn.disabled = false;
|
|
sendBtn.onclick = () => dc.send(sendMsg.value);
|
|
};
|
|
|
|
peerConnectionMap[id] = pc;
|
|
return pc;
|
|
}
|
|
|
|
// Setup a DataChannel
|
|
function setupDataChannel(dc, id) {
|
|
dc.onopen = () => {
|
|
console.log(`DataChannel from ${id} open`);
|
|
|
|
sendMsg.disabled = false;
|
|
sendBtn.disabled = false;
|
|
sendBtn.onclick = () => dc.send(sendMsg.value);
|
|
};
|
|
dc.onclose = () => {
|
|
console.log(`DataChannel from ${id} closed`);
|
|
};
|
|
dc.onmessage = (e) => {
|
|
if(typeof(e.data) != 'string') return;
|
|
console.log(`Message from ${id} received: ${e.data}`);
|
|
document.body.appendChild(document.createTextNode(e.data));
|
|
};
|
|
|
|
dataChannelMap[id] = dc;
|
|
return dc;
|
|
}
|
|
|
|
function sendLocalDescription(ws, id, pc, type) {
|
|
(type == 'offer' ? pc.createOffer() : pc.createAnswer())
|
|
.then((desc) => pc.setLocalDescription(desc))
|
|
.then(() => {
|
|
const { sdp, type } = pc.localDescription;
|
|
ws.send(JSON.stringify({
|
|
id,
|
|
type,
|
|
description: sdp,
|
|
}));
|
|
});
|
|
}
|
|
|
|
function sendLocalCandidate(ws, id, cand) {
|
|
const {candidate, sdpMid} = cand;
|
|
ws.send(JSON.stringify({
|
|
id,
|
|
type: 'candidate',
|
|
candidate,
|
|
mid: sdpMid,
|
|
}));
|
|
}
|
|
|
|
// Helper function to generate a random ID
|
|
function randomId(length) {
|
|
const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
|
|
return [...Array(length)].map(pickRandom).join('');
|
|
}
|
|
|
|
});
|
|
|