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.

LocalStatsCollector.js 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * Provides statistics for the local stream.
  3. */
  4. /**
  5. * Size of the webaudio analyzer buffer.
  6. * @type {number}
  7. */
  8. const WEBAUDIO_ANALYZER_FFT_SIZE = 2048;
  9. /**
  10. * Value of the webaudio analyzer smoothing time parameter.
  11. * @type {number}
  12. */
  13. const WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;
  14. window.AudioContext = window.AudioContext || window.webkitAudioContext;
  15. let context = null;
  16. if (window.AudioContext) {
  17. context = new AudioContext();
  18. // XXX Not all browsers define a suspend method on AudioContext. As the
  19. // invocation is at the (ES6 module) global execution level, it breaks the
  20. // loading of the lib-jitsi-meet library in such browsers and, consequently,
  21. // the loading of the very Web app that uses the lib-jitsi-meet library. For
  22. // example, Google Chrome 40 on Android does not define the method but we
  23. // still want to be able to load the lib-jitsi-meet library there and
  24. // display a page which notifies the user that the Web app is not supported
  25. // there.
  26. context.suspend && context.suspend();
  27. }
  28. /**
  29. * Converts time domain data array to audio level.
  30. * @param samples the time domain data array.
  31. * @returns {number} the audio level
  32. */
  33. function timeDomainDataToAudioLevel(samples) {
  34. let maxVolume = 0;
  35. const length = samples.length;
  36. for (let i = 0; i < length; i++) {
  37. if (maxVolume < samples[i]) {
  38. maxVolume = samples[i];
  39. }
  40. }
  41. return parseFloat(((maxVolume - 127) / 128).toFixed(3));
  42. }
  43. /**
  44. * Animates audio level change
  45. * @param newLevel the new audio level
  46. * @param lastLevel the last audio level
  47. * @returns {Number} the audio level to be set
  48. */
  49. function animateLevel(newLevel, lastLevel) {
  50. let value = 0;
  51. const diff = lastLevel - newLevel;
  52. if (diff > 0.2) {
  53. value = lastLevel - 0.2;
  54. } else if (diff < -0.4) {
  55. value = lastLevel + 0.4;
  56. } else {
  57. value = newLevel;
  58. }
  59. return parseFloat(value.toFixed(3));
  60. }
  61. /**
  62. * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
  63. *
  64. * @param stream the local stream
  65. * @param interval stats refresh interval given in ms.
  66. * @param callback function that receives the audio levels.
  67. * @constructor
  68. */
  69. export default function LocalStatsCollector(stream, interval, callback) {
  70. this.stream = stream;
  71. this.intervalId = null;
  72. this.intervalMilis = interval;
  73. this.audioLevel = 0;
  74. this.callback = callback;
  75. }
  76. /**
  77. * Starts the collecting the statistics.
  78. */
  79. LocalStatsCollector.prototype.start = function() {
  80. if (!LocalStatsCollector.isLocalStatsSupported()) {
  81. return;
  82. }
  83. context.resume();
  84. const analyser = context.createAnalyser();
  85. analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
  86. analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
  87. const source = context.createMediaStreamSource(this.stream);
  88. source.connect(analyser);
  89. const self = this;
  90. this.intervalId = setInterval(
  91. () => {
  92. const array = new Uint8Array(analyser.frequencyBinCount);
  93. analyser.getByteTimeDomainData(array);
  94. const audioLevel = timeDomainDataToAudioLevel(array);
  95. if (audioLevel !== self.audioLevel) {
  96. self.audioLevel = animateLevel(audioLevel, self.audioLevel);
  97. self.callback(self.audioLevel);
  98. }
  99. },
  100. this.intervalMilis
  101. );
  102. };
  103. /**
  104. * Stops collecting the statistics.
  105. */
  106. LocalStatsCollector.prototype.stop = function() {
  107. if (this.intervalId) {
  108. clearInterval(this.intervalId);
  109. this.intervalId = null;
  110. }
  111. };
  112. /**
  113. * Checks if the environment has the necessary conditions to support
  114. * collecting stats from local streams.
  115. *
  116. * @returns {boolean}
  117. */
  118. LocalStatsCollector.isLocalStatsSupported = function() {
  119. return Boolean(context);
  120. };