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.

CallStats.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /* global $, Strophe, callstats */
  2. var logger = require("jitsi-meet-logger").getLogger(__filename);
  3. var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
  4. var jsSHA = require('jssha');
  5. var io = require('socket.io-client');
  6. /**
  7. * We define enumeration of wrtcFuncNames as we need them before
  8. * callstats is initialized to queue events.
  9. * @const
  10. * @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames
  11. */
  12. var wrtcFuncNames = {
  13. createOffer: "createOffer",
  14. createAnswer: "createAnswer",
  15. setLocalDescription: "setLocalDescription",
  16. setRemoteDescription: "setRemoteDescription",
  17. addIceCandidate: "addIceCandidate",
  18. getUserMedia: "getUserMedia",
  19. iceConnectionFailure: "iceConnectionFailure",
  20. signalingError: "signalingError",
  21. applicationError: "applicationError"
  22. };
  23. /**
  24. * We define enumeration of fabricEvent as we need them before
  25. * callstats is initialized to queue events.
  26. * @const
  27. * @see http://www.callstats.io/api/#enumeration-of-fabricevent
  28. */
  29. var fabricEvent = {
  30. fabricHold:"fabricHold",
  31. fabricResume:"fabricResume",
  32. audioMute:"audioMute",
  33. audioUnmute:"audioUnmute",
  34. videoPause:"videoPause",
  35. videoResume:"videoResume",
  36. fabricUsageEvent:"fabricUsageEvent",
  37. fabricStats:"fabricStats",
  38. fabricTerminated:"fabricTerminated",
  39. screenShareStart:"screenShareStart",
  40. screenShareStop:"screenShareStop",
  41. dominantSpeaker:"dominantSpeaker",
  42. activeDeviceList:"activeDeviceList"
  43. };
  44. var callStats = null;
  45. function initCallback (err, msg) {
  46. logger.log("CallStats Status: err=" + err + " msg=" + msg);
  47. // there is no lib, nothing to report to
  48. if (err !== 'success')
  49. return;
  50. // notify callstats about failures if there were any
  51. if (CallStats.reportsQueue.length) {
  52. CallStats.reportsQueue.forEach(function (report) {
  53. if (report.type === reportType.ERROR)
  54. {
  55. var error = report.data;
  56. CallStats._reportError.call(this, error.type, error.error,
  57. error.pc);
  58. }
  59. else if (report.type === reportType.EVENT)
  60. {
  61. var data = report.data;
  62. callStats.sendFabricEvent(
  63. this.peerconnection,
  64. data.event,
  65. this.confID,
  66. data.eventData);
  67. }
  68. }, this);
  69. CallStats.reportsQueue.length = 0;
  70. }
  71. }
  72. /**
  73. * Returns a function which invokes f in a try/catch block, logs any exception
  74. * to the console, and then swallows it.
  75. *
  76. * @param f the function to invoke in a try/catch block
  77. * @return a function which invokes f in a try/catch block, logs any exception
  78. * to the console, and then swallows it
  79. */
  80. function _try_catch (f) {
  81. return function () {
  82. try {
  83. f.apply(this, arguments);
  84. } catch (e) {
  85. GlobalOnErrorHandler.callErrorHandler(e);
  86. logger.error(e);
  87. }
  88. };
  89. }
  90. /**
  91. * Creates new CallStats instance that handles all callstats API calls.
  92. * @param peerConnection {JingleSessionPC} the session object
  93. * @param Settings {Settings} the settings instance. Declared in
  94. * /modules/settings/Settings.js
  95. * @param options {object} credentials for callstats.
  96. */
  97. var CallStats = _try_catch(function(jingleSession, Settings, options) {
  98. try{
  99. //check weather that should work with more than 1 peerconnection
  100. if(!callStats) {
  101. callStats = new callstats($, io, jsSHA);
  102. } else {
  103. return;
  104. }
  105. this.session = jingleSession;
  106. this.peerconnection = jingleSession.peerconnection.peerconnection;
  107. this.userID = Settings.getCallStatsUserName();
  108. var location = window.location;
  109. // The confID is case sensitive!!!
  110. this.confID = location.hostname + "/" + options.roomName;
  111. //userID is generated or given by the origin server
  112. callStats.initialize(options.callStatsID,
  113. options.callStatsSecret,
  114. this.userID,
  115. initCallback.bind(this));
  116. callStats.addNewFabric(this.peerconnection,
  117. Strophe.getResourceFromJid(jingleSession.peerjid),
  118. callStats.fabricUsage.multiplex,
  119. this.confID,
  120. this.pcCallback.bind(this));
  121. } catch (e) {
  122. // The callstats.io API failed to initialize (e.g. because its
  123. // download failed to succeed in general or on time). Further
  124. // attempts to utilize it cannot possibly succeed.
  125. GlobalOnErrorHandler.callErrorHandler(e);
  126. callStats = null;
  127. logger.error(e);
  128. }
  129. });
  130. // some errors/events may happen before CallStats init
  131. // in this case we accumulate them in this array
  132. // and send them to callstats on init
  133. CallStats.reportsQueue = [];
  134. /**
  135. * Type of pending reports, can be event or an error.
  136. * @type {{ERROR: string, EVENT: string}}
  137. */
  138. var reportType = {
  139. ERROR: "error",
  140. EVENT: "event"
  141. };
  142. CallStats.prototype.pcCallback = _try_catch(function (err, msg) {
  143. if (!callStats) {
  144. return;
  145. }
  146. logger.log("Monitoring status: "+ err + " msg: " + msg);
  147. });
  148. /**
  149. * Lets CallStats module know where is given SSRC rendered by providing renderer
  150. * tag ID.
  151. * @param ssrc {number} the SSRC of the stream
  152. * @param isLocal {boolean} <tt>true<tt> if this stream is local or
  153. * <tt>false</tt> otherwise.
  154. * @param usageLabel {string} meaningful usage label of this stream like
  155. * 'microphone', 'camera' or 'screen'.
  156. * @param containerId {string} the id of media 'audio' or 'video' tag which
  157. * renders the stream.
  158. */
  159. CallStats.prototype.associateStreamWithVideoTag =
  160. function (ssrc, isLocal, usageLabel, containerId) {
  161. if(!callStats) {
  162. return;
  163. }
  164. // 'focus' is default remote user ID for now
  165. var callStatsId = 'focus';
  166. if (isLocal) {
  167. callStatsId = this.userID;
  168. }
  169. _try_catch(function() {
  170. logger.debug(
  171. "Calling callStats.associateMstWithUserID with:",
  172. this.peerconnection,
  173. callStatsId,
  174. this.confID,
  175. ssrc,
  176. usageLabel,
  177. containerId
  178. );
  179. callStats.associateMstWithUserID(
  180. this.peerconnection,
  181. callStatsId,
  182. this.confID,
  183. ssrc,
  184. usageLabel,
  185. containerId
  186. );
  187. }).bind(this)();
  188. };
  189. /**
  190. * Notifies CallStats for mute events
  191. * @param mute {boolean} true for muted and false for not muted
  192. * @param type {String} "audio"/"video"
  193. * @param {CallStats} cs callstats instance related to the event
  194. */
  195. CallStats.sendMuteEvent = _try_catch(function (mute, type, cs) {
  196. var event = null;
  197. if (type === "video") {
  198. event = (mute? fabricEvent.videoPause : fabricEvent.videoResume);
  199. }
  200. else {
  201. event = (mute? fabricEvent.audioMute : fabricEvent.audioUnmute);
  202. }
  203. CallStats._reportEvent.call(cs, event);
  204. });
  205. /**
  206. * Notifies CallStats for screen sharing events
  207. * @param start {boolean} true for starting screen sharing and
  208. * false for not stopping
  209. * @param {CallStats} cs callstats instance related to the event
  210. */
  211. CallStats.sendScreenSharingEvent = _try_catch(function (start, cs) {
  212. CallStats._reportEvent.call(cs,
  213. start? fabricEvent.screenShareStart : fabricEvent.screenShareStop);
  214. });
  215. /**
  216. * Notifies CallStats that we are the new dominant speaker in the conference.
  217. * @param {CallStats} cs callstats instance related to the event
  218. */
  219. CallStats.sendDominantSpeakerEvent = _try_catch(function (cs) {
  220. CallStats._reportEvent.call(cs,
  221. fabricEvent.dominantSpeaker);
  222. });
  223. /**
  224. * Notifies CallStats about active device.
  225. * @param {{deviceList: {String:String}}} list of devices with their data
  226. * @param {CallStats} cs callstats instance related to the event
  227. */
  228. CallStats.sendАctiveDeviceListEvent = _try_catch(function (devicesData, cs) {
  229. CallStats._reportEvent.call(cs, fabricEvent.activeDeviceList, devicesData);
  230. });
  231. /**
  232. * Reports an error to callstats.
  233. *
  234. * @param type the type of the error, which will be one of the wrtcFuncNames
  235. * @param e the error
  236. * @param pc the peerconnection
  237. * @param eventData additional data to pass to event
  238. * @private
  239. */
  240. CallStats._reportEvent = function (event, eventData) {
  241. if (callStats) {
  242. callStats.sendFabricEvent(
  243. this.peerconnection, event, this.confID, eventData);
  244. } else {
  245. CallStats.reportsQueue.push({
  246. type: reportType.EVENT,
  247. data: {event: event, eventData: eventData}
  248. });
  249. }
  250. };
  251. /**
  252. * Notifies CallStats for connection setup errors
  253. */
  254. CallStats.prototype.sendTerminateEvent = _try_catch(function () {
  255. if(!callStats) {
  256. return;
  257. }
  258. callStats.sendFabricEvent(this.peerconnection,
  259. callStats.fabricEvent.fabricTerminated, this.confID);
  260. });
  261. /**
  262. * Notifies CallStats for ice connection failed
  263. * @param {RTCPeerConnection} pc connection on which failure occured.
  264. * @param {CallStats} cs callstats instance related to the error (optional)
  265. */
  266. CallStats.prototype.sendIceConnectionFailedEvent = _try_catch(function (pc, cs){
  267. CallStats._reportError.call(
  268. cs, wrtcFuncNames.iceConnectionFailure, null, pc);
  269. });
  270. /**
  271. * Sends the given feedback through CallStats.
  272. *
  273. * @param overallFeedback an integer between 1 and 5 indicating the
  274. * user feedback
  275. * @param detailedFeedback detailed feedback from the user. Not yet used
  276. */
  277. CallStats.prototype.sendFeedback = _try_catch(
  278. function(overallFeedback, detailedFeedback) {
  279. if(!callStats) {
  280. return;
  281. }
  282. var feedbackString = '{"userID":"' + this.userID + '"' +
  283. ', "overall":' + overallFeedback +
  284. ', "comment": "' + detailedFeedback + '"}';
  285. var feedbackJSON = JSON.parse(feedbackString);
  286. callStats.sendUserFeedback(this.confID, feedbackJSON);
  287. });
  288. /**
  289. * Reports an error to callstats.
  290. *
  291. * @param type the type of the error, which will be one of the wrtcFuncNames
  292. * @param e the error
  293. * @param pc the peerconnection
  294. * @private
  295. */
  296. CallStats._reportError = function (type, e, pc) {
  297. if(!e) {
  298. logger.warn("No error is passed!");
  299. e = new Error("Unknown error");
  300. }
  301. if (callStats) {
  302. callStats.reportError(pc, this.confID, type, e);
  303. } else {
  304. CallStats.reportsQueue.push({
  305. type: reportType.ERROR,
  306. data: { type: type, error: e, pc: pc}
  307. });
  308. }
  309. // else just ignore it
  310. };
  311. /**
  312. * Notifies CallStats that getUserMedia failed.
  313. *
  314. * @param {Error} e error to send
  315. * @param {CallStats} cs callstats instance related to the error (optional)
  316. */
  317. CallStats.sendGetUserMediaFailed = _try_catch(function (e, cs) {
  318. CallStats._reportError.call(cs, wrtcFuncNames.getUserMedia, e, null);
  319. });
  320. /**
  321. * Notifies CallStats that peer connection failed to create offer.
  322. *
  323. * @param {Error} e error to send
  324. * @param {RTCPeerConnection} pc connection on which failure occured.
  325. * @param {CallStats} cs callstats instance related to the error (optional)
  326. */
  327. CallStats.sendCreateOfferFailed = _try_catch(function (e, pc, cs) {
  328. CallStats._reportError.call(cs, wrtcFuncNames.createOffer, e, pc);
  329. });
  330. /**
  331. * Notifies CallStats that peer connection failed to create answer.
  332. *
  333. * @param {Error} e error to send
  334. * @param {RTCPeerConnection} pc connection on which failure occured.
  335. * @param {CallStats} cs callstats instance related to the error (optional)
  336. */
  337. CallStats.sendCreateAnswerFailed = _try_catch(function (e, pc, cs) {
  338. CallStats._reportError.call(cs, wrtcFuncNames.createAnswer, e, pc);
  339. });
  340. /**
  341. * Notifies CallStats that peer connection failed to set local description.
  342. *
  343. * @param {Error} e error to send
  344. * @param {RTCPeerConnection} pc connection on which failure occured.
  345. * @param {CallStats} cs callstats instance related to the error (optional)
  346. */
  347. CallStats.sendSetLocalDescFailed = _try_catch(function (e, pc, cs) {
  348. CallStats._reportError.call(cs, wrtcFuncNames.setLocalDescription, e, pc);
  349. });
  350. /**
  351. * Notifies CallStats that peer connection failed to set remote description.
  352. *
  353. * @param {Error} e error to send
  354. * @param {RTCPeerConnection} pc connection on which failure occured.
  355. * @param {CallStats} cs callstats instance related to the error (optional)
  356. */
  357. CallStats.sendSetRemoteDescFailed = _try_catch(function (e, pc, cs) {
  358. CallStats._reportError.call(cs, wrtcFuncNames.setRemoteDescription, e, pc);
  359. });
  360. /**
  361. * Notifies CallStats that peer connection failed to add ICE candidate.
  362. *
  363. * @param {Error} e error to send
  364. * @param {RTCPeerConnection} pc connection on which failure occured.
  365. * @param {CallStats} cs callstats instance related to the error (optional)
  366. */
  367. CallStats.sendAddIceCandidateFailed = _try_catch(function (e, pc, cs) {
  368. CallStats._reportError.call(cs, wrtcFuncNames.addIceCandidate, e, pc);
  369. });
  370. /**
  371. * Notifies CallStats that there is an unhandled error on the page.
  372. *
  373. * @param {Error} e error to send
  374. * @param {RTCPeerConnection} pc connection on which failure occured.
  375. * @param {CallStats} cs callstats instance related to the error (optional)
  376. */
  377. CallStats.sendUnhandledError = _try_catch(function (e, cs) {
  378. CallStats._reportError
  379. .call(cs, wrtcFuncNames.applicationError, e, null);
  380. });
  381. module.exports = CallStats;