You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
projectx/src/lib/webrtc_adaptor.js

1110 lines
39 KiB

/* eslint-disable */
/**
*
* @returns
*/
"use strict";
var adapter = require("./adapter");
// eslint-disable-next-line no-unused-vars
var WebRTCAdaptor = function(initialValues) {
class PeerStats {
constructor(streamId) {
this.streamId = streamId;
this.totalBytesReceivedCount = 0;
this.totalBytesSent = 0;
this.packetsLost = 0;
this.fractionLost = 0;
this.startTime = 0;
this.lastBytesReceived = 0;
this.lastBytesSent = 0;
this.currentTimestamp = 0;
this.janusScreen = null;
this.lastTime = 0;
this.timerId = 0;
this.firstByteSentCount = 0;
this.firstBytesReceivedCount = 0;
}
//kbits/sec
get averageOutgoingBitrate() {
return Math.floor(8 * (this.totalBytesSentCount - this.firstByteSentCount) / (this.currentTimestamp - this.startTime));
}
//kbits/sec
get averageIncomingBitrate() {
return Math.floor(8 * (this.totalBytesReceivedCount - this.firstBytesReceivedCount) / (this.currentTimestamp - this.startTime));
}
//kbits/sec
get currentOutgoingBitrate() {
return Math.floor(8 * (this.totalBytesSentCount - this.lastBytesSent) / (this.currentTimestamp - this.lastTime));
}
//kbits/sec
get currentIncomingBitrate() {
return Math.floor(8 * (this.totalBytesReceivedCount - this.lastBytesReceived) / (this.currentTimestamp - this.lastTime));
}
set currentTime(timestamp) {
this.lastTime = this.currentTimestamp;
this.currentTimestamp = timestamp;
if (this.startTime == 0) {
this.startTime = timestamp - 1; // do not have zero division error
}
}
set totalBytesReceived(bytesReceived) {
this.lastBytesReceived = this.totalBytesReceivedCount;
this.totalBytesReceivedCount = bytesReceived;
if (this.firstBytesReceivedCount == 0) {
this.firstBytesReceivedCount = bytesReceived;
}
}
set totalBytesSent(bytesSent) {
this.lastBytesSent = this.totalBytesSentCount;
this.totalBytesSentCount = bytesSent;
if (this.firstByteSentCount == 0) {
this.firstByteSentCount = bytesSent;
}
}
}
var thiz = this;
thiz.peerconnection_config = null;
thiz.sdp_constraints = null;
thiz.remotePeerConnection = new Array();
thiz.remotePeerConnectionStats = new Array();
thiz.remoteDescriptionSet = new Array();
thiz.iceCandidateList = new Array();
thiz.webSocketAdaptor = null;
thiz.roomName = null;
thiz.videoTrackSender = null;
thiz.audioTrackSender = null;
thiz.playStreamId = new Array();
thiz.micGainNode = null;
thiz.localStream = null;
thiz.bandwidth = 512; //default bandwidth kbps
thiz.isPlayMode = false;
thiz.debug = false;
/**
* The cam_location below is effective when camera and screen is send at the same time.
* possible values are top and bottom. It's on right all the time
*/
thiz.camera_location = "top"
/**
* The cam_margin below is effective when camera and screen is send at the same time.
* This is the margin value in px from the edges
*/
thiz.camera_margin = 15;
/**
* Thiz camera_percent is how large the camera view appear on the screen. It's %15 by default.
*/
thiz.camera_percent = 100;
for (var key in initialValues) {
if (initialValues.hasOwnProperty(key)) {
this[key] = initialValues[key];
}
}
thiz.localVideo = document.getElementById(thiz.localVideoId);
thiz.remoteVideo = document.getElementById(thiz.remoteVideoId);
if (!("WebSocket" in window)) {
console.log("WebSocket not supported.");
thiz.callbackError("WebSocketNotSupported");
return;
}
if (typeof navigator.mediaDevices == "undefined" && thiz.isPlayMode == false) {
console.log("Cannot open camera and mic because of unsecure context. Please Install SSL(https)");
thiz.callbackError("UnsecureContext");
return;
}
/**
* Get user media
*/
this.getUserMedia = function (mediaConstraints, audioConstraint) {
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(function (stream) {
//this trick, getting audio and video separately, make us add or remove tracks on the fly
var audioTrack = stream.getAudioTracks();
if (audioTrack.length > 0) {
stream.removeTrack(audioTrack[0]);
}
//add callback if desktop is sharing
if (mediaConstraints.video != "undefined"
// eslint-disable-next-line no-mixed-spaces-and-tabs
&& typeof mediaConstraints.video.mandatory != "undefined"
// eslint-disable-next-line no-mixed-spaces-and-tabs
&& typeof mediaConstraints.video.mandatory.chromeMediaSource != "undefined"
// eslint-disable-next-line no-mixed-spaces-and-tabs
&& mediaConstraints.video.mandatory.chromeMediaSource == "desktop") {
stream.getVideoTracks()[0].onended = function (event) {
thiz.callback("screen_share_stopped");
}
}
if (thiz.janusScreen) {
thiz.gotStream(thiz.janusScreen);
} else if (audioConstraint != "undefined" && audioConstraint != false) {
var media_audio_constraint = {audio: audioConstraint};
navigator.mediaDevices.getUserMedia(media_audio_constraint)
.then(function (audioStream) {
if (thiz.mediaConstraints != "undefined" && thiz.mediaConstraints.video == "screen+camera") {
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(function (cameraStream) {
//create a canvas element
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
//create video element for screen
//var screenVideo = document.getElementById('sourceVideo');
var screenVideo = document.createElement('video');
//TODO: check audio track
screenVideo.srcObject = stream;
screenVideo.play();
//create video element for camera
var cameraVideo = document.createElement('video');
//TODO: check audio track
cameraVideo.srcObject = cameraStream;
cameraVideo.play();
var canvasStream = canvas.captureStream(15);
canvasStream.addTrack(audioStream.getAudioTracks()[0]);
//call gotStream
thiz.gotStream(canvasStream);
//update the canvas
setInterval(function () {
//draw screen to canvas
canvas.width = screenVideo.videoWidth;
canvas.height = screenVideo.videoHeight;
canvasContext.drawImage(screenVideo, 0, 0, canvas.width, canvas.height);
var cameraWidth = screenVideo.videoWidth * (thiz.camera_percent / 100);
var cameraHeight = (cameraVideo.videoHeight / cameraVideo.videoWidth) * cameraWidth
var positionX = (canvas.width - cameraWidth) - thiz.camera_margin;
var positionY;
if (thiz.camera_location == "top") {
positionY = thiz.camera_margin;
} else { //if not top, make it bottom
//draw camera on right bottom corner
positionY = (canvas.height - cameraHeight) - thiz.camera_margin;
}
canvasContext.drawImage(cameraVideo, positionX, positionY, cameraWidth, cameraHeight);
}, 66);
})
.catch(function (error) {
thiz.callbackError(error.name, error.message);
});
} else {
stream.addTrack(audioStream.getAudioTracks()[0]);
thiz.gotStream(stream);
}
})
.catch(function (error) {
thiz.callbackError(error.name, error.message);
});
} else {
stream.addTrack(audioStream.getAudioTracks()[0]);
thiz.gotStream(stream);
}
})
.catch(function (error) {
thiz.callbackError(error.name, error.message);
});
}
this.openScreen = function (audioConstraint, openCamera) {
var callback = function (message) {
if (message.data == "rtcmulticonnection-extension-loaded") {
console.debug("rtcmulticonnection-extension-loaded parameter is received");
window.postMessage("get-sourceId", "*");
} else if (message.data == "PermissionDeniedError") {
console.debug("Permission denied error");
thiz.callbackError("screen_share_permission_denied");
} else if (message.data && message.data.sourceId) {
var mediaConstraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: message.data.sourceId,
},
optional: []
}
};
thiz.getUserMedia(mediaConstraints, audioConstraint);
//remove event listener
window.removeEventListener("message", callback);
}
}
window.addEventListener("message", callback, false);
window.postMessage("are-you-there", "*");
}
/**
* Open media stream, it may be screen, camera or audio
*/
this.openStream = function (mediaConstraints) {
thiz.mediaConstraints = mediaConstraints;
var audioConstraint = false;
if (typeof mediaConstraints.audio != "undefined" && mediaConstraints.audio != false) {
audioConstraint = mediaConstraints.audio;
}
if (typeof mediaConstraints.video != "undefined") {
if (mediaConstraints.video == "screen+camera" || mediaConstraints.video == "screen") {
this.openScreen(audioConstraint);
} else {
thiz.getUserMedia(mediaConstraints, audioConstraint);
}
} else {
console.error("MediaConstraint video is not defined");
thiz.callbackError("media_constraint_video_not_defined");
}
}
/**
* Closes stream, if you want to stop peer connection, call stop(streamId)
*/
this.closeStream = function () {
thiz.localStream.getVideoTracks().forEach(function (track) {
track.onended = null;
track.stop();
});
thiz.localStream.getAudioTracks().forEach(function (track) {
track.onended = null;
track.stop();
});
}
/**
* Checks chrome screen share extension is avaiable
* if exists it call callback with "screen_share_extension_available"
*/
this.checkExtension = function () {
var callback = function (message) {
if (message.data == "rtcmulticonnection-extension-loaded") {
thiz.callback("screen_share_extension_available");
window.removeEventListener("message", callback);
}
};
//add event listener for desktop capture
window.addEventListener("message", callback, false);
window.postMessage("are-you-there", "*");
};
/*
* Call check extension. Below function is called when this class is created
*/
thiz.checkExtension();
/*
* Below lines are executed as well when this class is created
*/
if (!this.isPlayMode && typeof thiz.mediaConstraints != "undefined" && this.localStream == null) {
if (typeof thiz.mediaConstraints.video != "undefined" && thiz.mediaConstraints.video != false) {
// if it is not play mode and media constraint is defined, try to get user media
if (thiz.mediaConstraints.audio.mandatory) {
//this case captures mic and video(audio(screen audio) + video(screen)) and then provide mute/unmute mic with
//enableMicInMixedAudio
navigator.mediaDevices.getUserMedia({audio: true, video: false}).then(function (micStream) {
navigator.mediaDevices.getUserMedia(thiz.mediaConstraints)
.then(function (stream) {
//console.debug("audio stream track count: " + audioStream.getAudioTracks().length);
var audioContext = new AudioContext();
var desktopSoundGainNode = audioContext.createGain();
desktopSoundGainNode.gain.value = 1;
var audioDestionation = audioContext.createMediaStreamDestination();
var audioSource = audioContext.createMediaStreamSource(stream);
audioSource.connect(desktopSoundGainNode);
thiz.micGainNode = audioContext.createGain();
thiz.micGainNode.gain.value = 1;
var audioSource2 = audioContext.createMediaStreamSource(micStream);
audioSource2.connect(thiz.micGainNode);
desktopSoundGainNode.connect(audioDestionation);
thiz.micGainNode.connect(audioDestionation);
stream.removeTrack(stream.getAudioTracks()[0]);
audioDestionation.stream.getAudioTracks().forEach(function (track) {
stream.addTrack(track);
});
console.debug("Running gotStream");
thiz.gotStream(stream);
}).catch(function (error) {
thiz.callbackError(error.name, error.message);
});
}).catch(function (error) {
thiz.callbackError(error.name, error.message);
});
} else {
//most of the times, this statement runs
thiz.openStream(thiz.mediaConstraints, thiz.mode);
}
} else {
// get only audio
var media_audio_constraint = {audio: thiz.mediaConstraints.audio};
navigator.mediaDevices.getUserMedia(media_audio_constraint)
.then(function (stream) {
thiz.gotStream(stream);
})
.catch(function (error) {
thiz.callbackError(error.name, error.message);
});
}
} else {
//just playing, it does not open any stream
if (thiz.webSocketAdaptor == null || thiz.webSocketAdaptor.isConnected() == false) {
thiz.webSocketAdaptor = new WebSocketAdaptor();
}
}
this.enableMicInMixedAudio = function (enable) {
if (thiz.micGainNode != null) {
if (enable) {
thiz.micGainNode.gain.value = 1;
} else {
thiz.micGainNode.gain.value = 0;
}
}
}
this.publish = function (streamId, token) {
var jsCmd = {
command: "publish",
streamId: streamId,
token: token,
video: { width: 1280, height: 720 },
audio: true,
};
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.joinRoom = function (roomName, streamId) {
thiz.roomName = roomName;
var jsCmd = {
command: "joinRoom",
room: roomName,
streamId: streamId,
}
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.play = function (streamId, token, roomId) {
thiz.playStreamId.push(streamId);
var jsCmd =
{
command: "play",
streamId: streamId,
token: token,
room: roomId,
}
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.stop = function (streamId) {
thiz.closePeerConnection(streamId);
var jsCmd = {
command: "stop",
streamId: streamId,
};
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.join = function (streamId) {
var jsCmd = {
command: "join",
streamId: streamId,
};
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.leaveFromRoom = function (roomName) {
thiz.roomName = roomName;
var jsCmd = {
command: "leaveFromRoom",
room: roomName,
};
console.log("leave request is sent for " + roomName);
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.leave = function (streamId) {
var jsCmd = {
command: "leave",
streamId: streamId,
};
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
thiz.closePeerConnection(streamId);
}
this.getStreamInfo = function (streamId) {
var jsCmd = {
command: "getStreamInfo",
streamId: streamId,
};
this.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
this.gotStream = function (stream) {
thiz.localStream = stream;
// thiz.localVideo.srcObject = stream;
if (thiz.webSocketAdaptor == null || thiz.webSocketAdaptor.isConnected() == false) {
thiz.webSocketAdaptor = new WebSocketAdaptor();
}
};
this.switchVideoCapture = function (streamId) {
var mediaConstraints = {
video: true,
audio: true
};
thiz.switchVideoSource(streamId, mediaConstraints, null);
}
this.switchDesktopCapture = function (streamId) {
var screenShareExtensionCallback = function (message) {
if (message.data == "rtcmulticonnection-extension-loaded") {
console.debug("rtcmulticonnection-extension-loaded parameter is received");
window.postMessage("get-sourceId", "*");
} else if (message.data == "PermissionDeniedError") {
console.debug("Permission denied error");
thiz.callbackError("screen_share_permission_denied");
} else if (message.data && message.data.sourceId) {
var mediaConstraints = {
audio: true,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: message.data.sourceId,
},
optional: []
}
};
thiz.switchVideoSource(streamId, mediaConstraints, function (event) {
thiz.callback("screen_share_stopped");
thiz.switchVideoCapture(streamId);
});
//remove event listener
window.removeEventListener("message", screenShareExtensionCallback);
}
}
//add event listener for desktop capture
window.addEventListener("message", screenShareExtensionCallback, false);
window.postMessage("are-you-there", "*");
}
thiz.arrangeStreams = function (stream, onEndedCallback) {
var videoTrack = thiz.localStream.getVideoTracks()[0];
thiz.localStream.removeTrack(videoTrack);
videoTrack.stop();
thiz.localStream.addTrack(stream.getVideoTracks()[0]);
// thiz.localVideo.srcObject = thiz.localStream;
if (onEndedCallback != null) {
stream.getVideoTracks()[0].onended = function (event) {
onEndedCallback(event);
}
}
}
this.switchVideoSource = function (streamId, mediaConstraints, onEndedCallback) {
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(function (stream) {
if (thiz.remotePeerConnection[streamId] != null) {
var videoTrackSender = thiz.remotePeerConnection[streamId].getSenders().find(function (s) {
return s.track.kind == "video";
});
videoTrackSender.replaceTrack(stream.getVideoTracks()[0]).then(function (result) {
thiz.arrangeStreams(stream, onEndedCallback);
}).catch(function (error) {
console.log(error.name);
});
} else {
thiz.arrangeStreams(stream, onEndedCallback);
}
})
.catch(function (error) {
thiz.callbackError(error.name);
});
}
this.onTrack = function (event, streamId) {
console.log("onTrack");
if (thiz.remoteVideo != null) {
//thiz.remoteVideo.srcObject = event.streams[0];
if (thiz.remoteVideo.srcObject !== event.streams[0]) {
thiz.remoteVideo.srcObject = event.streams[0];
console.log('Received remote stream');
}
} else {
var dataObj = {
track: event.streams[0],
streamId: streamId
}
thiz.callback("newStreamAvailable", dataObj);
}
}
this.iceCandidateReceived = function (event, streamId) {
if (event.candidate) {
var jsCmd = {
command: "takeCandidate",
streamId: streamId,
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate
};
if (thiz.debug) {
console.log("sending ice candiate for stream Id " + streamId);
console.log(JSON.stringify(event.candidate));
}
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}
}
this.initPeerConnection = function (streamId) {
if (thiz.remotePeerConnection[streamId] == null) {
var closedStreamId = streamId;
console.log("stream id in init peer connection: " + streamId + " close dstream id: " + closedStreamId);
thiz.remotePeerConnection[streamId] = new RTCPeerConnection(thiz.peerconnection_config);
thiz.remoteDescriptionSet[streamId] = false;
thiz.iceCandidateList[streamId] = new Array();
if (!thiz.playStreamId.includes(streamId)) {
thiz.remotePeerConnection[streamId].addStream(thiz.localStream);
}
thiz.remotePeerConnection[streamId].onicecandidate = function (event) {
thiz.iceCandidateReceived(event, closedStreamId);
}
thiz.remotePeerConnection[streamId].ontrack = function (event) {
thiz.onTrack(event, closedStreamId);
}
if (!thiz.isPlayMode) {
thiz.remotePeerConnection[streamId].oniceconnectionstatechange = function (event) {
if (thiz.remotePeerConnection[streamId].iceConnectionState == "connected") {
thiz.changeBandwidth(thiz.bandwidth, streamId).then(() => {
console.log("Bandwidth is changed to " + thiz.bandwidth);
})
.catch(e => console.error(e));
}
}
}
}
}
this.closePeerConnection = function (streamId) {
if (thiz.remotePeerConnection[streamId] != null
&& thiz.remotePeerConnection[streamId].signalingState != "closed") {
thiz.remotePeerConnection[streamId].close();
thiz.remotePeerConnection[streamId] = null;
delete thiz.remotePeerConnection[streamId];
var playStreamIndex = thiz.playStreamId.indexOf(streamId);
if (playStreamIndex != -1) {
thiz.playStreamId.splice(playStreamIndex, 1);
}
}
if (thiz.remotePeerConnectionStats[streamId] != null) {
clearInterval(thiz.remotePeerConnectionStats[streamId].timerId);
delete thiz.remotePeerConnectionStats[streamId];
}
}
this.signallingState = function (streamId) {
if (thiz.remotePeerConnection[streamId] != null) {
return thiz.remotePeerConnection[streamId].signalingState;
}
return null;
}
this.iceConnectionState = function (streamId) {
if (thiz.remotePeerConnection[streamId] != null) {
return thiz.remotePeerConnection[streamId].iceConnectionState;
}
return null;
}
this.gotDescription = function (configuration, streamId) {
thiz.remotePeerConnection[streamId]
.setLocalDescription(configuration)
.then(function (responose) {
console.debug("Set local description successfully for stream Id " + streamId);
var jsCmd = {
command: "takeConfiguration",
streamId: streamId,
type: configuration.type,
sdp: configuration.sdp
};
if (thiz.debug) {
console.debug("local sdp: ");
console.debug(configuration.sdp);
}
thiz.webSocketAdaptor.send(JSON.stringify(jsCmd));
}).catch(function (error) {
console.error("Cannot set local description. Error is: " + error);
});
}
this.turnOffLocalCamera = function () {
if (thiz.remotePeerConnection != null) {
var track = thiz.localStream.getVideoTracks()[0];
track.enabled = false;
} else {
this.callbackError("NoActiveConnection");
}
}
this.turnOnLocalCamera = function () {
if (thiz.remotePeerConnection != null) {
var track = thiz.localStream.getVideoTracks()[0];
track.enabled = true;
} else {
this.callbackError("NoActiveConnection");
}
}
this.muteLocalMic = function () {
if (thiz.remotePeerConnection != null) {
var track = thiz.localStream.getAudioTracks()[0];
track.enabled = false;
} else {
this.callbackError("NoActiveConnection");
}
}
/**
* if there is audio it calls callbackError with "AudioAlreadyActive" parameter
*/
this.unmuteLocalMic = function () {
if (thiz.remotePeerConnection != null) {
var track = thiz.localStream.getAudioTracks()[0];
track.enabled = true;
} else {
this.callbackError("NoActiveConnection");
}
}
this.takeConfiguration = function (idOfStream, configuration, typeOfConfiguration) {
var streamId = idOfStream
var type = typeOfConfiguration;
var conf = configuration;
thiz.initPeerConnection(streamId);
thiz.remotePeerConnection[streamId].setRemoteDescription(new RTCSessionDescription({
sdp: conf,
type: type
})).then(function (response) {
if (thiz.debug) {
console.debug("set remote description is succesfull with response: " + response + " for stream : "
+ streamId + " and type: " + type);
console.debug(conf);
}
thiz.remoteDescriptionSet[streamId] = true;
var length = thiz.iceCandidateList[streamId].length;
console.debug("Ice candidate list size to be added: " + length);
for (var i = 0; i < length; i++) {
thiz.addIceCandidate(streamId, thiz.iceCandidateList[streamId][i]);
}
thiz.iceCandidateList[streamId] = [];
if (type == "offer") {
//SDP constraints may be different in play mode
console.log("try to create answer for stream id: " + streamId);
thiz.remotePeerConnection[streamId].createAnswer(thiz.sdp_constraints)
.then(function (configuration) {
console.log("created answer for stream id: " + streamId);
thiz.gotDescription(configuration, streamId);
})
.catch(function (error) {
console.error("create answer error :" + error);
});
}
}).catch(function (error) {
if (thiz.debug) {
console.error("set remote description is failed with error: " + error);
}
if (error.toString().indexOf("InvalidAccessError") > -1 || error.toString().indexOf("setRemoteDescription") > -1) {
/**
* This error generally occurs in codec incompatibility.
* AMS for a now supports H.264 codec. This error happens when some browsers try to open it from VP8.
*/
thiz.callbackError("notSetRemoteDescription");
}
});
}
this.takeCandidate = function (idOfTheStream, tmpLabel, tmpCandidate) {
var streamId = idOfTheStream;
var label = tmpLabel;
var candidateSdp = tmpCandidate;
var candidate = new RTCIceCandidate({
sdpMLineIndex: label,
candidate: candidateSdp
});
thiz.initPeerConnection(streamId);
if (thiz.remoteDescriptionSet[streamId] == true) {
thiz.addIceCandidate(streamId, candidate);
} else {
console.debug("Ice candidate is added to list because remote description is not set yet");
thiz.iceCandidateList[streamId].push(candidate);
}
};
this.addIceCandidate = function (streamId, candidate) {
thiz.remotePeerConnection[streamId].addIceCandidate(candidate)
.then(function (response) {
if (thiz.debug) {
console.log("Candidate is added for stream " + streamId);
}
})
.catch(function (error) {
console.error("ice candiate cannot be added for stream id: " + streamId + " error is: " + error);
console.error(candidate);
});
};
this.startPublishing = function (idOfStream) {
var streamId = idOfStream;
thiz.initPeerConnection(streamId);
thiz.remotePeerConnection[streamId].createOffer(thiz.sdp_constraints)
.then(function (configuration) {
thiz.gotDescription(configuration, streamId);
})
.catch(function (error) {
console.error("create offer error for stream id: " + streamId + " error: " + error);
});
};
/**
* If we have multiple video tracks in coming versions, this method may cause some issues
*/
this.getVideoSender = function (streamId) {
var videoSender = null;
if ((adapter.browserDetails.browser === 'chrome' ||
(adapter.browserDetails.browser === 'firefox' &&
adapter.browserDetails.version >= 64)) &&
'RTCRtpSender' in window &&
'setParameters' in window.RTCRtpSender.prototype) {
const senders = thiz.remotePeerConnection[streamId].getSenders();
for (let i = 0; i < senders.length; i++) {
if (senders[i].track != null && senders[i].track.kind == "video") {
videoSender = senders[i];
break;
}
}
}
return videoSender;
}
/**
* bandwidth is in kbps
*/
this.changeBandwidth = function (bandwidth, streamId) {
var errorDefinition = "";
var videoSender = thiz.getVideoSender(streamId);
if (videoSender != null) {
const parameters = videoSender.getParameters();
if (!parameters.encodings) {
parameters.encodings = [{}];
}
if (bandwidth === 'unlimited') {
delete parameters.encodings[0].maxBitrate;
} else {
parameters.encodings[0].maxBitrate = bandwidth * 1000;
}
return videoSender.setParameters(parameters)
} else {
errorDefinition = "Video sender not found to change bandwidth";
}
return Promise.reject(errorDefinition);
};
this.getStats = function (streamId) {
thiz.remotePeerConnection[streamId].getStats(null).then(stats => {
var bytesReceived = 0;
var packetsLost = 0;
var fractionLost = 0;
var currentTime = 0;
var bytesSent = 0;
stats.forEach(value => {
if (value.type == "inbound-rtp") {
bytesReceived += value.bytesReceived;
packetsLost += value.packetsLost;
fractionLost += value.fractionLost;
currentTime = value.timestamp;
} else if (value.type == "outbound-rtp") {
bytesSent += value.bytesSent
currentTime = value.timestamp
}
});
thiz.remotePeerConnectionStats[streamId].totalBytesReceived = bytesReceived;
thiz.remotePeerConnectionStats[streamId].packetsLost = packetsLost;
thiz.remotePeerConnectionStats[streamId].fractionLost = fractionLost;
thiz.remotePeerConnectionStats[streamId].currentTime = currentTime;
thiz.remotePeerConnectionStats[streamId].totalBytesSent = bytesSent;
thiz.callback("updated_stats", thiz.remotePeerConnectionStats[streamId]);
});
}
this.enableStats = function (streamId) {
thiz.remotePeerConnectionStats[streamId] = new PeerStats(streamId);
thiz.remotePeerConnectionStats[streamId].timerId = setInterval(() => {
thiz.getStats(streamId);
}, 5000);
}
/**
* After calling this function, create new WebRTCAdaptor instance, don't use the the same objectone
* Because all streams are closed on server side as well when websocket connection is closed.
*/
this.closeWebSocket = function () {
for (var key in thiz.remotePeerConnection) {
thiz.remotePeerConnection[key].close();
}
//free the remote peer connection by initializing again
thiz.remotePeerConnection = new Array();
thiz.webSocketAdaptor.close();
}
function WebSocketAdaptor() {
var wsConn = new WebSocket(thiz.websocket_url);
var connected = false;
var pingTimerId = -1;
var clearPingTimer = function () {
if (pingTimerId != -1) {
if (thiz.debug) {
console.debug("Clearing ping message timer");
}
clearInterval(pingTimerId);
pingTimerId = -1;
}
}
var sendPing = function () {
var jsCmd = {
command: "ping"
};
wsConn.send(JSON.stringify(jsCmd));
}
this.close = function () {
wsConn.close();
}
wsConn.onopen = function () {
if (thiz.debug) {
console.log("websocket connected");
}
pingTimerId = setInterval(() => {
sendPing();
}, 3000);
connected = true;
thiz.callback("initialized");
}
this.send = function (text) {
if (wsConn.readyState == 0 || wsConn.readyState == 2 || wsConn.readyState == 3) {
thiz.callbackError("WebSocketNotConnected");
return;
}
wsConn.send(text);
console.log("sent message:" + text);
}
this.isConnected = function () {
return connected;
}
wsConn.onmessage = function (event) {
var obj = JSON.parse(event.data);
if (obj.command == "start") {
//this command is received first, when publishing so playmode is false
if (thiz.debug) {
console.debug("received start command");
}
thiz.startPublishing(obj.streamId);
} else if (obj.command == "takeCandidate") {
if (thiz.debug) {
console.debug("received ice candidate for stream id " + obj.streamId);
console.debug(obj.candidate);
}
thiz.takeCandidate(obj.streamId, obj.label, obj.candidate);
} else if (obj.command == "takeConfiguration") {
if (thiz.debug) {
console.log("received remote description type for stream id: " + obj.streamId + " type: " + obj.type);
}
thiz.takeConfiguration(obj.streamId, obj.sdp, obj.type);
} else if (obj.command == "stop") {
console.debug("Stop command received");
thiz.closePeerConnection(obj.streamId);
} else if (obj.command == "error") {
thiz.callbackError(obj.definition);
} else if (obj.command == "notification") {
thiz.callback(obj.definition, obj);
if (obj.definition == "play_finished" || obj.definition == "publish_finished") {
thiz.closePeerConnection(obj.streamId);
}
} else if (obj.command == "streamInformation") {
thiz.callback(obj.command, obj);
} else if (obj.command == "pong") {
thiz.callback(obj.command);
}
}
wsConn.onerror = function (error) {
console.log(" error occured: " + JSON.stringify(error));
clearPingTimer();
thiz.callbackError(error)
}
wsConn.onclose = function (event) {
connected = false;
console.log("connection closed.");
clearPingTimer();
thiz.callback("closed", event);
}
}
}
module.exports = WebRTCAdaptor