// Set up media stream constant and parameters.
// In this codelab, you will be streaming video only: "video: true".
// Audio will not be streamed because it is set to "audio: false" by default.
const mediaStreamConstraints = {
// Set up to exchange only video.
// Define initial start time of the call (defined as connection between peers).
// Define peer connections, streams and video elements.
const localVideo = document.getElementById('localVideo')
const remoteVideo = document.getElementById('remoteVideo')
// Define MediaStreams callbacks.
// Sets the MediaStream as the video element src.
function gotLocalMediaStream(mediaStream) {
localVideo.srcObject = mediaStream
localStream = mediaStream
trace('Received local stream.')
callButton.disabled = false // Enable call button.
// Handles error by logging a message to the console.
function handleLocalMediaStreamError(error) {
trace(`navigator.getUserMedia error: ${error.toString()}.`)
// Handles remote MediaStream success by adding it as the remoteVideo src.
function gotRemoteMediaStream(event) {
const mediaStream = event.stream
remoteVideo.srcObject = mediaStream
remoteStream = mediaStream
trace('Remote peer connection received remote stream.')
// Add behavior for video streams.
// Logs a message with the id and size of a video element.
function logVideoLoaded(event) {
const video = event.target
`${video.id} videoWidth: ${video.videoWidth}px, ` +
`videoHeight: ${video.videoHeight}px.`
// Logs a message with the id and size of a video element.
// This event is fired when video begins streaming.
function logResizedVideo(event) {
const elapsedTime = window.performance.now() - startTime
trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`)
localVideo.addEventListener('loadedmetadata', logVideoLoaded)
remoteVideo.addEventListener('loadedmetadata', logVideoLoaded)
remoteVideo.addEventListener('onresize', logResizedVideo)
// Define RTC peer connection behavior.
// Connects with new peer candidate.
function handleConnection(event) {
const peerConnection = event.target
const iceCandidate = event.candidate
const newIceCandidate = new RTCIceCandidate(iceCandidate)
const otherPeer = getOtherPeer(peerConnection)
.addIceCandidate(newIceCandidate)
handleConnectionSuccess(peerConnection)
handleConnectionFailure(peerConnection, error)
`${getPeerName(peerConnection)} ICE candidate:\n` +
`${event.candidate.candidate}.`
// Logs that the connection succeeded.
function handleConnectionSuccess(peerConnection) {
trace(`${getPeerName(peerConnection)} addIceCandidate success.`)
// Logs that the connection failed.
function handleConnectionFailure(peerConnection, error) {
`${getPeerName(peerConnection)} failed to add ICE Candidate:\n` +
// Logs changes to the connection state.
function handleConnectionChange(event) {
const peerConnection = event.target
console.log('ICE state change event: ', event)
`${getPeerName(peerConnection)} ICE state: ` +
`${peerConnection.iceConnectionState}.`
// Logs error when setting session description fails.
function setSessionDescriptionError(error) {
trace(`Failed to create session description: ${error.toString()}.`)
// Logs success when setting session description.
function setDescriptionSuccess(peerConnection, functionName) {
const peerName = getPeerName(peerConnection)
trace(`${peerName} ${functionName} complete.`)
// Logs success when localDescription is set.
function setLocalDescriptionSuccess(peerConnection) {
setDescriptionSuccess(peerConnection, 'setLocalDescription')
// Logs success when remoteDescription is set.
function setRemoteDescriptionSuccess(peerConnection) {
setDescriptionSuccess(peerConnection, 'setRemoteDescription')
// Logs offer creation and sets peer connection session descriptions.
function createdOffer(description) {
trace(`Offer from localPeerConnection:\n${description.sdp}`)
trace('localPeerConnection setLocalDescription start.')
.setLocalDescription(description)
setLocalDescriptionSuccess(localPeerConnection)
.catch(setSessionDescriptionError)
trace('remotePeerConnection setRemoteDescription start.')
.setRemoteDescription(description)
setRemoteDescriptionSuccess(remotePeerConnection)
.catch(setSessionDescriptionError)
trace('remotePeerConnection createAnswer start.')
.catch(setSessionDescriptionError)
// Logs answer to offer creation and sets peer connection session descriptions.
function createdAnswer(description) {
trace(`Answer from remotePeerConnection:\n${description.sdp}.`)
trace('remotePeerConnection setLocalDescription start.')
.setLocalDescription(description)
setLocalDescriptionSuccess(remotePeerConnection)
.catch(setSessionDescriptionError)
trace('localPeerConnection setRemoteDescription start.')
.setRemoteDescription(description)
setRemoteDescriptionSuccess(localPeerConnection)
.catch(setSessionDescriptionError)
// Define and add behavior to buttons.
// Define action buttons.
const startButton = document.getElementById('startButton')
const callButton = document.getElementById('callButton')
const hangupButton = document.getElementById('hangupButton')
// Set up initial action buttons status: disable call and hangup.
callButton.disabled = true
hangupButton.disabled = true
// Handles start button action: creates local MediaStream.
startButton.disabled = true
.getUserMedia(mediaStreamConstraints)
.then(gotLocalMediaStream)
.catch(handleLocalMediaStreamError)
trace('Requesting local stream.')
// Handles call button action: creates peer connection.
callButton.disabled = true
hangupButton.disabled = false
startTime = window.performance.now()
// Get local media stream tracks.
const videoTracks = localStream.getVideoTracks()
const audioTracks = localStream.getAudioTracks()
if (videoTracks.length > 0) {
trace(`Using video device: ${videoTracks[0].label}.`)
if (audioTracks.length > 0) {
trace(`Using audio device: ${audioTracks[0].label}.`)
const servers = null // Allows for RTC server configuration.
// Create peer connections and add behavior.
localPeerConnection = new RTCPeerConnection(servers)
trace('Created local peer connection object localPeerConnection.')
localPeerConnection.addEventListener('icecandidate', handleConnection)
localPeerConnection.addEventListener(
'iceconnectionstatechange',
remotePeerConnection = new RTCPeerConnection(servers)
trace('Created remote peer connection object remotePeerConnection.')
remotePeerConnection.addEventListener('icecandidate', handleConnection)
remotePeerConnection.addEventListener(
'iceconnectionstatechange',
remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream)
// Add local stream to connection and create offer to connect.
localPeerConnection.addStream(localStream)
trace('Added local stream to localPeerConnection.')
trace('localPeerConnection createOffer start.')
.createOffer(offerOptions)
.catch(setSessionDescriptionError)
// Handles hangup action: ends up call, closes connections and resets peers.
function hangupAction() {
localPeerConnection.close()
remotePeerConnection.close()
localPeerConnection = null
remotePeerConnection = null
hangupButton.disabled = true
callButton.disabled = false
// Add click event handlers for buttons.
startButton.addEventListener('click', startAction)
callButton.addEventListener('click', callAction)
hangupButton.addEventListener('click', hangupAction)
// Define helper functions.
// Gets the "other" peer connection.
function getOtherPeer(peerConnection) {
return peerConnection === localPeerConnection
// Gets the name of a certain peer connection.
function getPeerName(peerConnection) {
return peerConnection === localPeerConnection
// Logs an action (text) and the time when it happened on the console.
const now = (window.performance.now() / 1000).toFixed(3)