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.

NoAudioSignalDetection.js 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import EventEmitter from 'events';
  2. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  3. import * as DetectionEvents from './DetectionEvents';
  4. // We wait a certain time interval for constant silence input from the current device to account for
  5. // potential abnormalities and for a better use experience i.e. don't generate event the instant
  6. // an audio track is added to the tcr.
  7. // Potential improvement - add this as a configurable parameter.
  8. const SILENCE_PERIOD_MS = 4000;
  9. /**
  10. * Detect if there is no audio input on the current TraceAblePeerConnection selected track. The no audio
  11. * state must be constant for a configured amount of time in order for the event to be triggered.
  12. * @fires DetectionEvents.AUDIO_INPUT_STATE_CHANGE
  13. * @fires DetectionEvents.NO_AUDIO_INPUT
  14. */
  15. export default class NoAudioSignalDetection extends EventEmitter {
  16. /**
  17. * @param conference the JitsiConference instance that created us.
  18. * @constructor
  19. */
  20. constructor(conference) {
  21. super();
  22. this._conference = conference;
  23. this._timeoutTrigger = null;
  24. this._hasAudioInput = null;
  25. conference.statistics.addAudioLevelListener(this._audioLevel.bind(this));
  26. conference.on(JitsiConferenceEvents.TRACK_ADDED, this._trackAdded.bind(this));
  27. }
  28. /**
  29. * Clear the timeout state.
  30. */
  31. _clearTriggerTimeout() {
  32. clearTimeout(this._timeoutTrigger);
  33. this._timeoutTrigger = null;
  34. }
  35. /**
  36. * Generated event triggered by a change in the current conference audio input state.
  37. *
  38. * @param {*} audioLevel
  39. * @fires DetectionEvents.AUDIO_INPUT_STATE_CHANGE
  40. */
  41. _handleAudioInputStateChange(audioLevel) {
  42. // Current audio input state of the active local track in the conference, true for audio input false for no
  43. // audio input.
  44. const status = audioLevel !== 0;
  45. let shouldTrigger;
  46. // If we this is the first audio event picked up or the current status is different from the previous trigger
  47. // the event.
  48. if (this._hasAudioInput === null) {
  49. shouldTrigger = true;
  50. } else if (this._hasAudioInput !== status) {
  51. shouldTrigger = true;
  52. }
  53. if (shouldTrigger) {
  54. this._hasAudioInput = status;
  55. /**
  56. * Event fired when the audio input state of the conference changes, true for audio input false otherwise.
  57. *
  58. * @event DetectionEvents.AUDIO_INPUT_STATE_CHANGE
  59. * @type {boolean}
  60. */
  61. this.emit(DetectionEvents.AUDIO_INPUT_STATE_CHANGE, this._hasAudioInput);
  62. }
  63. }
  64. /**
  65. * Generate event triggered by a prolonged period of no audio input.
  66. *
  67. * @param {number} audioLevel - The audio level of the ssrc.
  68. * @fires DetectionEvents.NO_AUDIO_INPUT
  69. */
  70. _handleNoAudioInputDetection(audioLevel) {
  71. if (this._eventFired) {
  72. return;
  73. }
  74. if (audioLevel === 0 && !this._timeoutTrigger) {
  75. this._timeoutTrigger = setTimeout(() => {
  76. this._eventFired = true;
  77. /**
  78. * Event fired when there is no audio input for a predefined period of time.
  79. *
  80. * @event DetectionEvents.AUDIO_INPUT_STATE_CHANGE
  81. * @type {void}
  82. */
  83. this.emit(DetectionEvents.NO_AUDIO_INPUT);
  84. }, SILENCE_PERIOD_MS);
  85. } else if (audioLevel !== 0 && this._timeoutTrigger) {
  86. this._clearTriggerTimeout();
  87. }
  88. }
  89. /**
  90. * Receives audio level events for all send and receive streams on the current TraceablePeerConnection.
  91. *
  92. * @param {TraceablePeerConnection} tpc - TraceablePeerConnection of the owning conference.
  93. * @param {number} ssrc - The synchronization source identifier (SSRC) of the endpoint/participant/stream
  94. * being reported.
  95. * @param {number} audioLevel - The audio level of the ssrc.
  96. * @param {boolean} isLocal - true for local/send streams or false for remote/receive streams.
  97. */
  98. _audioLevel(tpc, ssrc, audioLevel, isLocal) {
  99. // We are interested in the local audio streams
  100. if (!isLocal || !this._audioTrack) {
  101. return;
  102. }
  103. // Get currently active local tracks from the TraceablePeerConnection
  104. const localSSRCs = tpc.localSSRCs.get(this._audioTrack.rtcId);
  105. // Check that currently selected audio stream has ssrc in the TraceablePeerConnection
  106. if (!localSSRCs) {
  107. return;
  108. }
  109. // Only target the current active track in the tpc. For some reason audio levels for previous
  110. // devices are also picked up from the PeerConnection so we filter them out.
  111. const isCurrentTrack = localSSRCs.ssrcs.includes(ssrc);
  112. if (!isCurrentTrack) {
  113. return;
  114. }
  115. // First handle audio input state change. In case the state changed to no input the no audio input event
  116. // can try to fire again.
  117. this._handleAudioInputStateChange(audioLevel);
  118. this._handleNoAudioInputDetection(audioLevel);
  119. }
  120. /**
  121. * Notifies NoAudioSignalDetection that a JitsiTrack was added to the associated JitsiConference.
  122. * Only take into account local audio tracks.
  123. *
  124. * @param {JitsiTrack} track - The added JitsiTrack.
  125. */
  126. _trackAdded(track) {
  127. if (track.isLocalAudioTrack()) {
  128. // Reset state for the new track.
  129. this._audioTrack = track;
  130. this._eventFired = false;
  131. this._clearTriggerTimeout();
  132. }
  133. }
  134. }