I am developing a cross-plattform application with peer.js and webrtc. I am using cordova, crosswalk. Additionaly I am using the webrtc adapter (https://github.com/webrtc/adapter)
My code is based on the webrtc-crosswalk sample. (https://github.com/crosswalk-project/crosswalk-samples)
I want to change the videosource of the stream without creating a new call. My approche is to remove the tracks of the stream and add the new tracks of the other camera. The result is that the local video shows the right content, but the callee's remote video freezes.
Probably I am doing a very basic mistake, but i can't find a solution. I am looking forward to your answers and solutions.
My main codefile is attached.
//Notwendig, um die Dialogfunktion zu aktivieren
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
console.log(navigator.notification);
// Now safe to use device APIs
}
document.addEventListener('DOMContentLoaded', function () {
// PeerJS server location
var SERVER_IP = '172.20.37.147';
var SERVER_PORT = 9000;
// DOM elements manipulated as user interacts with the app
var messageBox = document.querySelector('#messages');
var callerIdEntry = document.querySelector('#caller-id');
var connectBtn = document.querySelector('#connect');
var recipientIdEntry = document.querySelector('#recipient-id');
var dialBtn = document.querySelector('#dial');
var remoteVideo = document.querySelector('#remote-video');
var localVideo = document.querySelector('#local-video');
var cameraTurn = document.querySelector('#camera_turn');
var stop = document.querySelector('#stop');
// the default facing direction
var dir = "environment";
// the ID set for this client
var callerId = null;
// PeerJS object, instantiated when this client connects with its
// caller ID
var peer = null;
// the local video stream captured with getUserMedia()
var localStream = null;
// DOM utilities
var makePara = function (text) {
var p = document.createElement('p');
p.innerText = text;
return p;
};
var addMessage = function (para) {
if (messageBox.firstChild) {
messageBox.insertBefore(para, messageBox.firstChild);
}
else {
messageBox.appendChild(para);
}
};
var logError = function (text) {
var p = makePara('ERROR: ' + text);
p.style.color = 'red';
addMessage(p);
};
var logMessage = function (text) {
addMessage(makePara(text));
};
// get the local video and audio stream and show preview in the
// "LOCAL" video element
// successCb: has the signature successCb(stream); receives
// the local video stream as an argument
var getLocalStream = function (successCb, ask = true) {
if (localStream && successCb) {
successCb(localStream);
}
else {
navigator.mediaDevices.getUserMedia({ audio: true, video: { facingMode: dir } })
.then(function (stream) {
if (localStream == null) {
/* use the stream */
localStream = stream;
}
else {
stream.getTracks().forEach(function (track) {
localStream.addTrack(track);
});
}
localVideo.src = window.URL.createObjectURL(localStream);
if (successCb) {
successCb(stream);
}
})
.catch(function (err) {
/* handle the error */
logError('failed to access local camera');
logError(err.message);
});
}
};
// set the "REMOTE" video element source
var showRemoteStream = function (stream) {
remoteVideo.src = window.URL.createObjectURL(stream);
};
// set caller ID and connect to the PeerJS server
var connect = function () {
callerId = callerIdEntry.value;
if (!callerId) {
logError('please set caller ID first');
return;
}
try {
// create connection to the ID server
peer = new Peer(callerId, { host: SERVER_IP, port: SERVER_PORT });
// hack to get around the fact that if a server connection cannot
// be established, the peer and its socket property both still have
// open === true; instead, listen to the wrapped WebSocket
// and show an error if its readyState becomes CLOSED
peer.socket._socket.onclose = function () {
logError('no connection to server');
peer = null;
};
// get local stream ready for incoming calls once the wrapped
// WebSocket is open
peer.socket._socket.onopen = function () {
getLocalStream();
};
// handle events representing incoming calls
peer.on('call', answer);
}
catch (e) {
peer = null;
logError('error while connecting to server');
}
};
// make an outgoing call
var dial = function () {
if (!peer) {
logError('please connect first');
return;
}
if (!localStream) {
logError('could not start call as there is no local camera');
return
}
var recipientId = recipientIdEntry.value;
if (!recipientId) {
logError('could not start call as no recipient ID is set');
return;
}
getLocalStream(function (stream) {
logMessage('outgoing call initiated');
var call = peer.call(recipientId, stream);
call.on('stream', showRemoteStream);
call.on('error', function (e) {
logError('error with call');
logError(e.message);
});
});
};
// answer an incoming call
var answer = function (call) {
if (!peer) {
logError('cannot answer a call without a connection');
return;
}
if (!localStream) {
logError('could not answer call as there is no localStream ready');
return;
}
//Asks user to answer the call
navigator.notification.confirm(
"Receive a call?",
function (buttonIndex) {
if (buttonIndex === 1) {
//user clicked "yes"
logMessage('incoming call answered');
call.on('stream', showRemoteStream);
call.answer(localStream);
}
else {
//user clicked "no"
logMessage('incoming call denied');
}
}
,
'Incoming Call',
['Yes', 'No']
);
};
function turnDirection() {
if (dir === "user")
return "environment";
else
return "user";
}
var turnCamera = function (call) {
dir = turnDirection();
localStream.getTracks().forEach(function (track) {
track.stop();
localStream.removeTrack(track);
});
getLocalStream(false);
};
var stopCall = function (call) { };
// wire up button events
connectBtn.addEventListener('click', connect);
dialBtn.addEventListener('click', dial);
cameraTurn.addEventListener('click', turnCamera);
stop.addEventListener('click', stopCall);
});
If you remove and then add a new track to a PeerConnection you need to renegotiate the offer-answer to get it working. I will recommend you to use the replaceTrack API to avoid the re-negotiation problem while changing the camera input.