123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- /*
- * 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('');
- }
- });
|