Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="video-players">
  <video playsinline muted id="peer1"></video>
  <video playsinline muted id="peer2"></video>
</div>
<div class="controls">
  <button id="get-permissions">Get permissions</button>
  <button disabled id="start-call">Start call</button>
  <button disabled id="hang-call">Hang up</button>
  <button id="share-button">Mute camera</button>
</div>
<div id="log-container">
  <h2>Logs</h2>
  <div id="logs"></div>
</div>
              
            
!

CSS

              
                body {
  background-color: #67568c;
  padding: 1rem;
  font-family: Helvetica;

}
#video-players {
 display: inline-block;
 margin: 0 auto;
 border: 4px solid #ff6e6c;
  border-radius: 4px;
}

.controls {
  margin-top: 0.5rem;  
}

.controls > button {
  border: none;
  margin: 0;
  padding: 1rem;
  border-radius: 1rem;
  
  color: white;
  font-size: 1rem;
    font-weight: 500;
}



#get-permissions {
  background-color: #fbdd74;
  color: black;

}

#start-call {
  background-color: green;
}

#hang-call {
  background-color: red;
}

#share-button {
  display: block;
  margin-top: 1rem;
  background-color: #ff6e6c;
}

#stats-button {
  display: block;
  margin-top: 1rem;
  background-color: #ff6e6c;
}

#hang-call:disabled, #start-call:disabled, #get-permissions:disabled {
  background-color: grey;
}

#log-container {
  color: #ff6e6c;
}
              
            
!

JS

              
                function wrapPeerConnection(prefixesToWrap) {
  prefixesToWrap.forEach((prefix) => {
    const originalPeerConnection = window[prefix + "RTCPeerConnection"];
    if (!originalPeerConnection) {
      return;
    }
    const newPeerConnection = function (config) {
      if (
        !config.iceServers ||
        !config.iceServers.length ||
        !config.iceServers[0].urls
      ) {
        // Invalid iceServers object, aborting
        return new originalPeerConnection(config);
      }
      const newConfig = {
        ...config,
        iceServers: config.iceServers.map((i) => ({
          ...i,
          urls: i.urls.filter((u) => u.includes("udp"))
        }))
      };
      return new originalPeerConnection(newConfig);
    };

    const originalAddIceCandidate =
      originalPeerConnection.prototype.addIceCandidate;
    const newAddIceCandidate = function (candidate) {
      log(`fixing ice candidates`);

      if (candidate.candidate.includes("tcp")) {
        return;
      }
      return originalAddIceCandidate.apply(this, [candidate]);
    };
    originalPeerConnection.prototype.addIceCandidate = newAddIceCandidate;

    window[prefix + "RTCPeerConnection"] = newPeerConnection;
    window[prefix + "RTCPeerConnection"].prototype =
      originalPeerConnection.prototype;
  });
}
wrapPeerConnection(["", "webkit", "moz"]);

// App state
let localStream;
let pc1;
let pc2;
const constraints = { audio: true, video: true };
const offerOptions = {
  offerToReceiveAudio: 1,
  offerToReceiveVideo: 1
};
const sdpSemantics = {};

// DOM Nodes
const videos = [
  document.getElementById("peer1"),
  document.getElementById("peer2")
];

videos[0].addEventListener("pause", () => videos[0].play());

const getPermissions = document.getElementById("get-permissions");
const startCallButton = document.getElementById("start-call");
const endCallButton = document.getElementById("hang-call");
const shareScreenButton = document.getElementById("share-button");

const logContainer = document.getElementById("logs");

// Helpers
const log = (content, isError) => {
  const time = new Date();
  logContainer.innerHTML += `<p ${
    isError ? 'style="background: red; color: black;"' : ""
  }>[${time.toLocaleTimeString()}] ${content}</p>`;
  if (typeof content === "object" && content !== null) {
    console.log(content);
  }
};

const getName = (pc) => (pc === pc1 ? "pc1" : "pc2");
const getOtherPc = (pc) => (pc === pc1 ? pc2 : pc1);

// Handlers
const errorHandler = (error) => {
  log(`getUserMedia error: ${error.name}`, true);
};

const onIceCandidate = async (pc, event) => {
  try {
    await getOtherPc(pc).addIceCandidate(event.candidate);
    log(`${getName(pc)} addIceCandidate success`);
  } catch (e) {
    log(`${getName(pc)} failed to add ICE Candidate: ${e.toString()}`, true);
  }
  log(
    `${getName(pc)} ICE candidate:\n${
      event.candidate ? event.candidate.candidate : "(null)"
    }`
  );
};

const onIceStateChange = (pc, event) => {
  if (pc) {
    log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
    log("ICE state change event: ");
    log(event);
  }
};

const gotRemoteStream = async (event) => {
  if (videos[1].srcObject !== event.streams[0]) {
    videos[1].srcObject = event.streams[0];
    log("pc2 received remote stream");
    await videos[1].play();
  }
};

const onCreateAnswerSuccess = async (desc) => {
  log(`Answer from pc2:\n${desc.sdp}`);
  log("pc2 setLocalDescription start");
  try {
    await pc2.setLocalDescription(desc);
    log(`${getName(pc2)} setLocalDescription complete`);
  } catch (e) {
    log(`Failed to set session description: ${e.toString()}`, true);
  }
  console.log("pc1 setRemoteDescription start");
  try {
    await pc1.setRemoteDescription(desc);
    log(`${getName(pc1)} setRemoteDescription complete`);
  } catch (e) {
    log(`Failed to set session description: ${e.toString()}`, true);
  }
};

const onCreateOfferSuccess = async (desc) => {
  log(`Offer from pc1\n${desc.sdp}`);
  log("pc1 setLocalDescription start");
  try {
    await pc1.setLocalDescription(desc);
    log(`${getName(pc1)} setLocalDescription complete`);
  } catch (e) {
    log(`Failed to set session description: ${e.toString()}`, true);
  }

  log("pc2 setRemoteDescription start");
  try {
    await pc2.setRemoteDescription(desc);
    log(`${getName(pc2)} setRemoteDescription complete`);
  } catch (e) {
    log(`Failed to set session description: ${e.toString()}`, true);
  }

  log("pc2 createAnswer start");
  // Since the 'remote' side has no media stream we need
  // to pass in the right constraints in order for it to
  // accept the incoming offer of audio and video.
  try {
    const answer = await pc2.createAnswer();
    await onCreateAnswerSuccess(answer);
  } catch (e) {
    log(`Failed to set session description: ${e.toString()}`, true);
  }
};

const startCall = async () => {
  endCallButton.disabled = false;
  startCallButton.disabled = true;

  const videoTracks = localStream.getVideoTracks();
  const audioTracks = localStream.getAudioTracks();
  if (videoTracks.length > 0) {
    log(`Using video device: ${videoTracks[0].label}`);
  }
  if (audioTracks.length > 0) {
    log(`Using audio device: ${audioTracks[0].label}`);
  }
  log("RTCPeerConnection configuration:");
  log(sdpSemantics);

  pc1 = new RTCPeerConnection(sdpSemantics);
  log("Created pc1");
  pc1.addEventListener("icecandidate", (e) => onIceCandidate(pc1, e));
  pc2 = new RTCPeerConnection(sdpSemantics);
  log("Created pc2");
  pc2.addEventListener("icecandidate", (e) => onIceCandidate(pc2, e));
  pc1.addEventListener("iceconnectionstatechange", (e) =>
    onIceStateChange(pc1, e)
  );
  pc2.addEventListener("iceconnectionstatechange", (e) =>
    onIceStateChange(pc2, e)
  );
  pc2.addEventListener("track", gotRemoteStream);

  localStream.getTracks().forEach((track) => pc1.addTrack(track, localStream));
  log("Added local stream to pc1");

  try {
    log("pc1 createOffer start");
    const offer = await pc1.createOffer(offerOptions);
    log(offer);
    await onCreateOfferSuccess(offer);
  } catch (e) {
    log(`Failed to create session description: ${e.toString()}`);
  }
};

const endCall = () => {
  log("Hanging up");
  pc1.close();
  pc2.close();
  pc1 = null;
  pc2 = null;
  startCallButton.disabled = false;
  endCallButton.disabled = true;
};

const askForPermissions = async () => {
  try {
    localStream = await navigator.mediaDevices.getUserMedia(constraints);
    localStream.getTracks().forEach((track) => {
      track.onmute = (event) => {
        log(`track ${track.kind} muted with message: ${event.message}`);
        log(event);
      };
      track.onunmute = (event) => {
        log(`track ${track.kind} unmuted with message: ${event.message}`);
        log(event);
      };
    });
    videos[0].srcObject = localStream;
    log("permissions acquired");
    await videos[0].play();

    getPermissions.disabled = true;
    startCallButton.disabled = false;
  } catch (err) {
    errorHandler(err);
  }
};

const shareButtonHandler = async () => {
  log("toggle mute camera");
  localStream.getVideoTracks().forEach((t) => (t.enabled = !t.enabled));
};

getPermissions.addEventListener("click", askForPermissions);
startCallButton.addEventListener("click", startCall);
endCallButton.addEventListener("click", endCall);
shareScreenButton.addEventListener("click", shareButtonHandler);

              
            
!
999px

Console