client.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. const iceConnectionLog = document.getElementById('ice-connection-state'),
  2. iceGatheringLog = document.getElementById('ice-gathering-state'),
  3. signalingLog = document.getElementById('signaling-state'),
  4. dataChannelLog = document.getElementById('data-channel');
  5. const clientId = randomId(10);
  6. const websocket = new WebSocket('ws://127.0.0.1:8000/' + clientId);
  7. websocket.onopen = () => {
  8. document.getElementById('start').disabled = false;
  9. }
  10. websocket.onmessage = async (evt) => {
  11. if (typeof evt.data !== 'string') {
  12. return;
  13. }
  14. const message = JSON.parse(evt.data);
  15. if (message.type == "offer") {
  16. document.getElementById('offer-sdp').textContent = message.sdp;
  17. await handleOffer(message)
  18. }
  19. }
  20. let pc = null;
  21. let dc = null;
  22. function createPeerConnection() {
  23. const config = {
  24. bundlePolicy: "max-bundle",
  25. };
  26. if (document.getElementById('use-stun').checked) {
  27. config.iceServers = [{urls: ['stun:stun.l.google.com:19302']}];
  28. }
  29. let pc = new RTCPeerConnection(config);
  30. // Register some listeners to help debugging
  31. pc.addEventListener('iceconnectionstatechange', () =>
  32. iceConnectionLog.textContent += ' -> ' + pc.iceConnectionState);
  33. iceConnectionLog.textContent = pc.iceConnectionState;
  34. pc.addEventListener('icegatheringstatechange', () =>
  35. iceGatheringLog.textContent += ' -> ' + pc.iceGatheringState);
  36. iceGatheringLog.textContent = pc.iceGatheringState;
  37. pc.addEventListener('signalingstatechange', () =>
  38. signalingLog.textContent += ' -> ' + pc.signalingState);
  39. signalingLog.textContent = pc.signalingState;
  40. // Receive audio/video track
  41. pc.ontrack = (evt) => {
  42. document.getElementById('media').style.display = 'block';
  43. const video = document.getElementById('video');
  44. // always overrite the last stream - you may want to do something more clever in practice
  45. video.srcObject = evt.streams[0]; // The stream groups audio and video tracks
  46. video.play();
  47. };
  48. // Receive data channel
  49. pc.ondatachannel = (evt) => {
  50. dc = evt.channel;
  51. dc.onopen = () => {
  52. dataChannelLog.textContent += '- open\n';
  53. dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
  54. };
  55. let dcTimeout = null;
  56. dc.onmessage = (evt) => {
  57. if (typeof evt.data !== 'string') {
  58. return;
  59. }
  60. dataChannelLog.textContent += '< ' + evt.data + '\n';
  61. dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
  62. dcTimeout = setTimeout(() => {
  63. if (!dc) {
  64. return;
  65. }
  66. const message = `Pong ${currentTimestamp()}`;
  67. dataChannelLog.textContent += '> ' + message + '\n';
  68. dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
  69. dc.send(message);
  70. }, 1000);
  71. }
  72. dc.onclose = () => {
  73. clearTimeout(dcTimeout);
  74. dcTimeout = null;
  75. dataChannelLog.textContent += '- close\n';
  76. dataChannelLog.scrollTop = dataChannelLog.scrollHeight;
  77. };
  78. }
  79. return pc;
  80. }
  81. async function waitGatheringComplete() {
  82. return new Promise((resolve) => {
  83. if (pc.iceGatheringState === 'complete') {
  84. resolve();
  85. } else {
  86. pc.addEventListener('icegatheringstatechange', () => {
  87. if (pc.iceGatheringState === 'complete') {
  88. resolve();
  89. }
  90. });
  91. }
  92. });
  93. }
  94. async function sendAnswer(pc) {
  95. await pc.setLocalDescription(await pc.createAnswer());
  96. await waitGatheringComplete();
  97. const answer = pc.localDescription;
  98. document.getElementById('answer-sdp').textContent = answer.sdp;
  99. websocket.send(JSON.stringify({
  100. id: "server",
  101. type: answer.type,
  102. sdp: answer.sdp,
  103. }));
  104. }
  105. async function handleOffer(offer) {
  106. pc = createPeerConnection();
  107. await pc.setRemoteDescription(offer);
  108. await sendAnswer(pc);
  109. }
  110. function sendRequest() {
  111. websocket.send(JSON.stringify({
  112. id: "server",
  113. type: "request",
  114. }));
  115. }
  116. function start() {
  117. document.getElementById('start').style.display = 'none';
  118. document.getElementById('stop').style.display = 'inline-block';
  119. document.getElementById('media').style.display = 'block';
  120. sendRequest();
  121. }
  122. function stop() {
  123. document.getElementById('stop').style.display = 'none';
  124. document.getElementById('media').style.display = 'none';
  125. document.getElementById('start').style.display = 'inline-block';
  126. // close data channel
  127. if (dc) {
  128. dc.close();
  129. dc = null;
  130. }
  131. // close transceivers
  132. if (pc.getTransceivers) {
  133. pc.getTransceivers().forEach((transceiver) => {
  134. if (transceiver.stop) {
  135. transceiver.stop();
  136. }
  137. });
  138. }
  139. // close local audio/video
  140. pc.getSenders().forEach((sender) => {
  141. const track = sender.track;
  142. if (track !== null) {
  143. sender.track.stop();
  144. }
  145. });
  146. // close peer connection
  147. pc.close();
  148. pc = null;
  149. }
  150. // Helper function to generate a random ID
  151. function randomId(length) {
  152. const characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  153. const pickRandom = () => characters.charAt(Math.floor(Math.random() * characters.length));
  154. return [...Array(length) ].map(pickRandom).join('');
  155. }
  156. // Helper function to generate a timestamp
  157. let startTime = null;
  158. function currentTimestamp() {
  159. if (startTime === null) {
  160. startTime = Date.now();
  161. return 0;
  162. } else {
  163. return Date.now() - startTime;
  164. }
  165. }