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 7.4KB

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