script.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. * libdatachannel example web client
  3. * Copyright (C) 2020 Lara Mackey
  4. * Copyright (C) 2020 Paul-Louis Ageneau
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. window.addEventListener('load', () => {
  20. const config = {
  21. iceServers : [ {
  22. urls : 'stun:stun.l.google.com:19302', // change to your STUN server
  23. } ],
  24. };
  25. const localId = randomId(4);
  26. const url = `ws://localhost:8000/${localId}`;
  27. const peerConnectionMap = {};
  28. const dataChannelMap = {};
  29. const offerId = document.getElementById('offerId');
  30. const offerBtn = document.getElementById('offerBtn');
  31. const sendMsg = document.getElementById('sendMsg');
  32. const sendBtn = document.getElementById('sendBtn');
  33. const _localId = document.getElementById('localId');
  34. _localId.textContent = localId;
  35. console.log('Connecting to signaling...');
  36. openSignaling(url)
  37. .then((ws) => {
  38. console.log('WebSocket connected, signaling ready');
  39. offerId.disabled = false;
  40. offerBtn.disabled = false;
  41. offerBtn.onclick = () => offerPeerConnection(ws, offerId.value);
  42. })
  43. .catch((err) => console.error(err));
  44. function openSignaling(url) {
  45. return new Promise((resolve, reject) => {
  46. const ws = new WebSocket(url);
  47. ws.onopen = () => resolve(ws);
  48. ws.onerror = () => reject(new Error('WebSocket error'));
  49. ws.onclose = () => console.error('WebSocket disconnected');
  50. ws.onmessage = (e) => {
  51. if (typeof (e.data) != 'string')
  52. return;
  53. const message = JSON.parse(e.data);
  54. console.log(message);
  55. const {id, type} = message;
  56. let pc = peerConnectionMap[id];
  57. if (!pc) {
  58. if (type != 'offer')
  59. return;
  60. // Create PeerConnection for answer
  61. console.log(`Answering to ${id}`);
  62. pc = createPeerConnection(ws, id);
  63. }
  64. switch (type) {
  65. case 'offer':
  66. case 'answer':
  67. pc.setRemoteDescription({
  68. sdp : message.description,
  69. type : message.type,
  70. }).then(() => {
  71. if (type == 'offer') {
  72. // Send answer
  73. sendLocalDescription(ws, id, pc, 'answer');
  74. }
  75. });
  76. break;
  77. case 'candidate':
  78. pc.addIceCandidate({
  79. candidate : message.candidate,
  80. sdpMid : message.mid,
  81. });
  82. break;
  83. }
  84. }
  85. });
  86. }
  87. function offerPeerConnection(ws, id) {
  88. // Create PeerConnection
  89. console.log(`Offering to ${id}`);
  90. pc = createPeerConnection(ws, id);
  91. // Create DataChannel
  92. const label = "test";
  93. console.log(`Creating DataChannel with label "${label}"`);
  94. const dc = pc.createDataChannel(label);
  95. setupDataChannel(dc, id);
  96. // Send offer
  97. sendLocalDescription(ws, id, pc, 'offer');
  98. }
  99. // Create and setup a PeerConnection
  100. function createPeerConnection(ws, id) {
  101. const pc = new RTCPeerConnection(config);
  102. pc.oniceconnectionstatechange = () => console.log(`Connection state: ${pc.iceConnectionState}`);
  103. pc.onicegatheringstatechange = () => console.log(`Gathering state: ${pc.iceGatheringState}`);
  104. pc.onicecandidate = (e) => {
  105. if (e.candidate && e.candidate.candidate) {
  106. // Send candidate
  107. sendLocalCandidate(ws, id, e.candidate);
  108. }
  109. };
  110. pc.ondatachannel = (e) => {
  111. const dc = e.channel;
  112. console.log(`"DataChannel from ${id} received with label "${dc.label}"`);
  113. setupDataChannel(dc, id);
  114. dc.send(`Hello from ${localId}`);
  115. sendMsg.disabled = false;
  116. sendBtn.disabled = false;
  117. sendBtn.onclick = () => dc.send(sendMsg.value);
  118. };
  119. peerConnectionMap[id] = pc;
  120. return pc;
  121. }
  122. // Setup a DataChannel
  123. function setupDataChannel(dc, id) {
  124. dc.onopen = () => {
  125. console.log(`DataChannel from ${id} open`);
  126. sendMsg.disabled = false;
  127. sendBtn.disabled = false;
  128. sendBtn.onclick = () => dc.send(sendMsg.value);
  129. };
  130. dc.onclose = () => { console.log(`DataChannel from ${id} closed`); };
  131. dc.onmessage = (e) => {
  132. if (typeof (e.data) != 'string')
  133. return;
  134. console.log(`Message from ${id} received: ${e.data}`);
  135. document.body.appendChild(document.createTextNode(e.data));
  136. };
  137. dataChannelMap[id] = dc;
  138. return dc;
  139. }
  140. function sendLocalDescription(ws, id, pc, type) {
  141. (type == 'offer' ? pc.createOffer() : pc.createAnswer())
  142. .then((desc) => pc.setLocalDescription(desc))
  143. .then(() => {
  144. const {sdp, type} = pc.localDescription;
  145. ws.send(JSON.stringify({
  146. id,
  147. type,
  148. description : sdp,
  149. }));
  150. });
  151. }
  152. function sendLocalCandidate(ws, id, cand) {
  153. const {candidate, sdpMid} = cand;
  154. ws.send(JSON.stringify({
  155. id,
  156. type : 'candidate',
  157. candidate,
  158. mid : sdpMid,
  159. }));
  160. }
  161. // Helper function to generate a random ID
  162. function randomId(length) {
  163. const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  164. const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
  165. return [...Array(length) ].map(pickRandom).join('');
  166. }
  167. });