script.js 5.0 KB

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