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.

connectionquality.js 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /* global config */
  2. import EventEmitter from "events";
  3. import CQEvents from "../../service/connectionquality/CQEvents";
  4. const eventEmitter = new EventEmitter();
  5. /**
  6. * local stats
  7. * @type {{}}
  8. */
  9. var stats = {};
  10. /**
  11. * remote stats
  12. * @type {{}}
  13. */
  14. var remoteStats = {};
  15. /**
  16. * Quality percent( 100% - good, 0% - bad.) for the local user.
  17. */
  18. var localConnectionQuality = 100;
  19. /**
  20. * Quality percent( 100% - good, 0% - bad.) stored per id.
  21. */
  22. var remoteConnectionQuality = {};
  23. /**
  24. * Calculates the quality percent based on passed new and old value.
  25. * @param newVal the new value
  26. * @param oldVal the old value
  27. */
  28. function calculateQuality(newVal, oldVal) {
  29. return (newVal <= oldVal) ? newVal : (9*oldVal + newVal) / 10;
  30. }
  31. // webrtc table describing simulcast resolutions and used bandwidth
  32. // https://chromium.googlesource.com/external/webrtc/+/master/webrtc/media/engine/simulcast.cc#42
  33. const _bandwidthMap = [
  34. { width: 1920, height: 1080, layers:3, max: 5000, min: 800 },
  35. { width: 1280, height: 720, layers:3, max: 2500, min: 600 },
  36. { width: 960, height: 540, layers:3, max: 900, min: 450 },
  37. { width: 640, height: 360, layers:2, max: 700, min: 150 },
  38. { width: 480, height: 270, layers:2, max: 450, min: 150 },
  39. { width: 320, height: 180, layers:1, max: 200, min: 30 }
  40. ];
  41. /**
  42. * We disable quality calculations based on bandwidth if simulcast is disabled,
  43. * or enable it in case of no simulcast and we force it.
  44. * @type {boolean}
  45. */
  46. const disableQualityBasedOnBandwidth =
  47. config.forceQualityBasedOnBandwidth ? false : config.disableSimulcast;
  48. /**
  49. * Calculates the quality percentage based on the input resolution height and
  50. * the upload reported by the client. The value is based on the interval from
  51. * _bandwidthMap.
  52. * @param inputHeight the resolution used to open the camera.
  53. * @param upload the upload rate reported by client.
  54. * @returns {int} the percent of upload based on _bandwidthMap and maximum value
  55. * of 100, as values of the map are approximate and clients can stream above
  56. * those values. Returns undefined if no result is found.
  57. */
  58. function calculateQualityUsingUpload(inputHeight, upload) {
  59. // found resolution from _bandwidthMap which height is equal or less than
  60. // the inputHeight
  61. let foundResolution = _bandwidthMap.find((r) => (r.height <= inputHeight));
  62. if (!foundResolution)
  63. return undefined;
  64. if (upload <= foundResolution.min)
  65. return 0;
  66. return Math.min(
  67. ((upload - foundResolution.min)*100)
  68. / (foundResolution.max - foundResolution.min),
  69. 100);
  70. }
  71. export default {
  72. /**
  73. * Updates the local statistics
  74. * @param data new statistics
  75. * @param dontUpdateLocalConnectionQuality {boolean} if true -
  76. * localConnectionQuality wont be recalculated.
  77. * @param videoType the local video type
  78. * @param isMuted current state of local video, whether it is muted
  79. * @param resolution the current resolution used by local video
  80. */
  81. updateLocalStats:
  82. function (data, dontUpdateLocalConnectionQuality,
  83. videoType, isMuted, resolution) {
  84. stats = data;
  85. if(!dontUpdateLocalConnectionQuality) {
  86. let val = this._getNewQualityValue(
  87. stats,
  88. localConnectionQuality,
  89. videoType,
  90. isMuted,
  91. resolution);
  92. if (val !== undefined)
  93. localConnectionQuality = val;
  94. }
  95. eventEmitter.emit(
  96. CQEvents.LOCALSTATS_UPDATED, localConnectionQuality, stats);
  97. },
  98. /**
  99. * Updates only the localConnectionQuality value
  100. * @param values {int} the new value. should be from 0 - 100.
  101. */
  102. updateLocalConnectionQuality: function (value) {
  103. localConnectionQuality = value;
  104. eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, localConnectionQuality,
  105. stats);
  106. },
  107. /**
  108. * Updates remote statistics
  109. * @param id the id associated with the statistics
  110. * @param data the statistics received
  111. * @param remoteVideoType the video type of the remote video
  112. * @param isRemoteVideoMuted whether remote video is muted
  113. */
  114. updateRemoteStats:
  115. function (id, data, remoteVideoType, isRemoteVideoMuted) {
  116. if (!data ||
  117. !("packetLoss" in data) ||
  118. !("total" in data.packetLoss)) {
  119. eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, id, null, null);
  120. return;
  121. }
  122. let inputResolution = data.resolution;
  123. // Use only the fields we need
  124. data = {bitrate: data.bitrate, packetLoss: data.packetLoss};
  125. remoteStats[id] = data;
  126. let val = this._getNewQualityValue(
  127. data,
  128. remoteConnectionQuality[id],
  129. remoteVideoType,
  130. isRemoteVideoMuted,
  131. inputResolution);
  132. if (val !== undefined)
  133. remoteConnectionQuality[id] = val;
  134. eventEmitter.emit(
  135. CQEvents.REMOTESTATS_UPDATED, id,
  136. remoteConnectionQuality[id], remoteStats[id]);
  137. },
  138. /**
  139. * Returns the new quality value based on the input parameters.
  140. * Used to calculate remote and local values.
  141. * @param data the data
  142. * @param lastQualityValue the last value we calculated
  143. * @param videoType need to check whether we are screen sharing
  144. * @param isMuted is video muted
  145. * @param resolution the input resolution used by the camera
  146. * @returns {*} the newly calculated value or undefined if no result
  147. * @private
  148. */
  149. _getNewQualityValue:
  150. function (data, lastQualityValue, videoType, isMuted, resolution) {
  151. if (disableQualityBasedOnBandwidth
  152. || isMuted
  153. || videoType === 'desktop'
  154. || !resolution) {
  155. return calculateQuality(
  156. 100 - data.packetLoss.total,
  157. lastQualityValue || 100);
  158. } else {
  159. return calculateQualityUsingUpload(
  160. resolution,
  161. data.bitrate.upload);
  162. }
  163. },
  164. /**
  165. * Returns the local statistics.
  166. */
  167. getStats: function () {
  168. return stats;
  169. },
  170. addListener: function (type, listener) {
  171. eventEmitter.on(type, listener);
  172. }
  173. };