let socket = io();
socket.on(`connect`, () => {
console.log(`new User Connected`);
});
jQuery(`#video-form`).on(`submit`, (event) => {
event.preventDefault();
socket.emit(`offer`, () => {
console.log(`Starting call`);
});
});
/////////////////////////////
'use strict';
let isChannelReady = false;
let isInitiator = false;
let isStarted = false;
let localStream;
let pc;
let remoteStream;
let turnReady;
let pcConfig = {
'iceServers': [{
'url': 'stun:stun.1.google.com:19302'
}]
};
// Set up audio and video regardless of what devices are present.
let sdpConstraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
};
/////////////////////////////////////////////
let room = 'foo';
// Could prompt for room name:
// room = prompt('Enter room name:');
if (room !== '') {
socket.emit('create or join', room);
console.log('Attempted to create or join room', room);
}
socket.on('created', function (room) {
console.log('Created room ' + room);
isInitiator = true;
});
socket.on('full', function (room) {
console.log('Room ' + room + ' is full');
});
socket.on('join', function (room) {
console.log('Another peer made a request to join room ' + room);
console.log('This peer is the initiator of room ' + room + '!');
isChannelReady = true;
});
socket.on('joined', function (room) {
console.log('joined: ' + room);
isChannelReady = true;
});
socket.on('log', function (array) {
console.log.apply(console, array);
});
////////////////////////////////////////////////
function sendMessage(message) {
console.log('Client sending message: ', message);
socket.emit('message', message);
}
// This client receives a message
socket.on('message', function (message) {
console.log('Client received message:', message);
if (message === 'got user media') {
//isStarted = true;
isInitiator = true;
isChannelReady = true;
maybeStart(message);
} else if (message.type === 'offer') {
if (!isInitiator && !isStarted) {
maybeStart(message);
}
pc.setRemoteDescription(new RTCSessionDescription(message));
doAnswer(message);
} else if (message.type === 'answer' && isStarted) {
pc.setRemoteDescription(new RTCSessionDescription(message));
} else if (message.type === 'candidate' && isStarted) {
let candidate = new RTCIceCandidate({
sdpMLineIndex: message.label,
candidate: message.candidate
});
pc.addIceCandidate(candidate);
} else if (message === 'bye' && isStarted) {
handleRemoteHangup();
}
});
////////////////////////////////////////////////////
let localVideo = document.querySelector('#localVideo');
let remoteVideo = document.querySelector('#remoteVideo');
navigator.getUserMedia = navigator.getUserMedia || navigator.mediaDevices.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
let constraints = {
audio: false,
video: true
};
navigator.mediaDevices.getUserMedia(constraints)
.then(gotStream)
.catch(function (e) {
alert('getUserMedia() error: ' + e.name);
});
function gotStream(stream) {
console.log('Adding local stream.');
// if (navigator.webkitGetUserMedia) {
localVideo.src = window.URL.createObjectURL(stream);
// }
// else {
// localStream.src = stream;
// }
localStream = stream;
sendMessage('got user media');
if (isInitiator) {
maybeStart();
}
}
console.log('Getting user media with constraints', constraints);
if (location.hostname !== 'localhost') {
requestTurn();
}
function maybeStart(message) {
// isChannelReady = true;
// isInitiator = true;
console.log('>>>>>>> maybeStart() ', isStarted, localStream, isChannelReady);
if (!isStarted && typeof localStream !== 'undefined' && isChannelReady) {
console.log('>>>>>> creating peer connection');
createPeerConnection(message);
pc.addStream(localStream);
isStarted = true;
console.log('isInitiator', isInitiator);
if (isInitiator) {
doCall();
}
}
}
window.onbeforeunload = function () {
sendMessage('bye');
};
/////////////////////////////////////////////////////////
function createPeerConnection(message) {
try {
//let msg = JSON.parse(message.data);
pc = new RTCPeerConnection(pcConfig) || webkitRTCPeerConnection(pcConfig) || mozRTCPeerConnection(pcConfig);
pc.setRemoteDescription(new RTCSessionDescription(message));
console.log(`SetRemomteDescription`);
pc.onicecandidate = handleIceCandidate;
console.log(`handleIceCandidate`);
pc.onaddstream = handleRemoteStreamAdded;
console.log(`handleRemoteStreamAdde`);
pc.onremovestream = handleRemoteStreamRemoved;
console.log('Created RTCPeerConnnection');
} catch (e) {
console.log('Failed to create PeerConnection, exception: ' + e.message);
alert('Cannot create RTCPeerConnection object.');
}
}
function handleIceCandidate(event) {
console.log('icecandidate event: ', event);
if (event.candidate) {
sendMessage({
type: 'candidate',
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
});
} else {
console.log('End of candidates.');
}
}
function handleRemoteStreamAdded(event) {
//if (navigator.webkitGetUserMedia) {
console.log('Remote stream added.');
remoteVideo.src = window.URL.createObjectURL(event.stream);
// }
// else {
// remoteStream.srcObject = event.stream;
// }
remoteStream = event.stream;
}
function handleCreateOfferError(event) {
console.log('createOffer() error: ', event);
}
function doCall() {
console.log('Sending offer to peer');
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
function doAnswer(message) {
console.log('Sending answer to peer.');
pc.createAnswer(message.id).then(
setLocalAndSendMessage).catch(
onCreateSessionDescriptionError);
}
function setLocalAndSendMessage(sessionDescription) {
// Set Opus as the preferred codec in SDP if Opus is present.
// sessionDescription.sdp = preferOpus(sessionDescription.sdp);
pc.setLocalDescription(sessionDescription);
console.log('setLocalAndSendMessage sending message', sessionDescription);
sendMessage(sessionDescription);
}
function onCreateSessionDescriptionError(error) {
trace('Failed to create session description: ' + error.toString());
}
function requestTurn(turnURL) {
let turnExists = false;
for (let i in pcConfig.iceServers) {
if (pcConfig.iceServers[i].url.substr(0, 5) === 'turn:') {
turnExists = true;
turnReady = true;
break;
}
}
if (!turnExists) {
console.log('Getting TURN server from ', turnURL);
// No TURN server. Get one from computeengineondemand.appspot.com:
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let turnServer = JSON.parse(xhr.responseText);
console.log('Got TURN server: ', turnServer);
pcConfig.iceServers.push({
'url': 'turn:192.158.29.39:3478?transport=tcp', //+ turnServer.username + '@' + turnServer.turn,
'username':'28224511:1379330808',
'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA='
});
turnReady = true;
}
};
//xhr.open('GET', turnURL, true);
//xhr.send();
}
}
// function handleRemoteStreamAdded(event) {
// console.log('Remote stream added.');
// remoteVideo.src = window.URL.createObjectURL(event.stream);
// remoteStream = event.stream;
// }
function handleRemoteStreamRemoved(event) {
console.log('Remote stream removed. Event: ', event);
}
function hangup() {
console.log('Hanging up.');
stop();
sendMessage('bye');
}
function handleRemoteHangup() {
console.log('Session terminated.');
stop();
isInitiator = false;
}
function stop() {
isStarted = false;
// isAudioMuted = false;
// isVideoMuted = false;
pc.close();
pc = null;
}
///////////////////////////////////////////
// Set Opus as the default audio codec if it's present.
function preferOpus(sdp) {
let sdpLines = sdp.split('\r\n');
let mLineIndex;
// Search for m line.
for (let i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('m=audio') !== -1) {
mLineIndex = i;
break;
}
}
if (mLineIndex === null) {
return sdp;
}
// If Opus is available, set it as the default in m line.
for (let i = 0; i < sdpLines.length; i++) {
if (sdpLines[i].search('opus/48000') !== -1) {
let opusPayload = extractSdp(sdpLines[i], /:(\d+) opus\/48000/i);
if (opusPayload) {
sdpLines[mLineIndex] = setDefaultCodec(sdpLines[mLineIndex],
opusPayload);
}
break;
}
}
// Remove CN in m line and sdp.
sdpLines = removeCN(sdpLines, mLineIndex);
sdp = sdpLines.join('\r\n');
return sdp;
}
function extractSdp(sdpLine, pattern) {
let result = sdpLine.match(pattern);
return result && result.length === 2 ? result[1] : null;
}
// Set the selected codec to the first in m line.
function setDefaultCodec(mLine, payload) {
let elements = mLine.split(' ');
let newLine = [];
let index = 0;
for (let i = 0; i < elements.length; i++) {
if (index === 3) { // Format of media starts from the fourth.
newLine[index++] = payload; // Put target payload to the first.
}
if (elements[i] !== payload) {
newLine[index++] = elements[i];
}
}
return newLine.join(' ');
}
// Strip CN from sdp before CN constraints is ready.
function removeCN(sdpLines, mLineIndex) {
let mLineElements = sdpLines[mLineIndex].split(' ');
// Scan from end for the convenience of removing an item.
for (let i = sdpLines.length - 1; i >= 0; i--) {
let payload = extractSdp(sdpLines[i], /a=rtpmap:(\d+) CN\/\d+/i);
if (payload) {
let cnPos = mLineElements.indexOf(payload);
if (cnPos !== -1) {
// Remove CN payload from m line.
mLineElements.splice(cnPos, 1);
}
// Remove CN line in sdp
sdpLines.splice(i, 1);
}
}
sdpLines[mLineIndex] = mLineElements.join(' ');
return sdpLines;
}
Backend Code:
const path = require(`path`);
const http = require(`http`);
const express = require(`express`);
const socketIO = require(`socket.io`);
const {generateMessage} = require(`./utils/message`);
const {isRealString} = require(`./utils/validation`);
const publicPath = path.join(__dirname,`../public`);
let app = express();
let server = http.createServer(app);
let io = socketIO(server);
app.use(express.static(publicPath));
app.get(`/video`,(req,res)=>{
res.sendFile(path.join(__dirname,`../public/videochat.html`));
});
io.on(`connection`,(socket)=>{
console.log(`New User Connected`);
socket.emit(`newMessage`,generateMessage(`Admin`, `Welcome to Chat App`));
socket.broadcast.emit(`newMessage`,generateMessage(`Admin`,`New User Joined`));
socket.on(`join`,(params,callback)=>{
if(!isRealString(params.name) || !isRealString(params.room)){
callback(`Name and Room name is required`);
}
callback();
});
socket.on('createMessage',(message,callback)=>{
console.log(`create Message`,message);
io.emit(`newMessage`,generateMessage(message.from,message.text));
callback();
});
socket.on(`offer`,(callback)=>{
console.log(`Call is Starting`);
callback();
});
/////////////////////////////////////////
function log() {
let array = ['Message from server:'];
array.push.apply(array, arguments);
socket.emit('log', array);
}
socket.on('message', function (message) {
log('Client said: ', message);
// for a real app, would be room-only (not broadcast)
socket.emit('message', message);
});
socket.on(`sdp`,(data)=>{
console.log('Received SDP from ' + socket.id);
socket.emit('sdp received', data.sdp);
});
socket.on('create or join', function (room) {
log('Received request to create or join room ' + room);
let numClients = io.sockets.sockets.length;
log('Room ' + room + ' now has ' + numClients + ' client(s)');
if (numClients === 1) {
socket.join(room);
log('Client ID ' + socket.id + ' created room ' + room);
socket.emit('created', room, socket.id);
} else if (numClients === 2) {
log('Client ID ' + socket.id + ' joined room ' + room);
io.sockets.in(room).emit('join', room);
socket.join(room);
socket.emit('joined', room, socket.id);
io.sockets.in(room).emit('ready');
} else { // max two clients
socket.emit('full', room);
}
});
socket.on('ipaddr', function () {
let ifaces = os.networkInterfaces();
for (let dev in ifaces) {
ifaces[dev].forEach(function (details) {
if (details.family === 'IPv4' && details.address !== '127.0.0.1') {
socket.emit('ipaddr', details.address);
}
});
}
});
socket.on('bye', function () {
console.log('received bye');
});
});
server.listen(3000,`192.168.0.105`,()=>{
console.log(`Server is Up`)
});
I'm getting this Error:- Failed to create PeerConnection, exception: Failed to construct 'RTCSessionDescription': parameter 1 ('descriptionInitDict') is not an object.
Also getting Uncaught (in promise) DOMException: Failed to set remote offer sdp: Called in wrong state: kHaveLocalOffer
This line:
function doCall() {
console.log('Sending offer to peer');
pc.createOffer(setLocalAndSendMessage, handleCreateOfferError);
}
Change to:
pc.createOffer().then(setDescription).catch(errorHandler);
function setDescription(description) {
console.log('Got description', description);
pc.setLocalDescription(description).then(function() {
console.log("Sending SDP");
socket.emit('signal', {'sdp': description});
}).catch(errorHandler);
}
You should than handle the emitted SDP:
function gotMessageFromServer(signal) {
console.log('Got message', signal);
if(signal.sdp) {
console.log("Set remote description: ", signal);
pc.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
// Only create answers in response to offers
if(signal.sdp.type === 'offer') {
console.log("Sending answer");
pc.createAnswer().then(setDescription).catch(errorHandler);
}
}).catch(errorHandler);
} else if(signal.ice) {
console.log('Got remote ice candidate: ', signal);
pc.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
}
}