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.

ActiveDeviceDetector.js 3.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { getLogger } from '@jitsi/logger';
  2. import * as JitsiTrackEvents from '../../JitsiTrackEvents';
  3. import RTC from '../RTC/RTC';
  4. import Statistics from '../statistics/statistics';
  5. const logger = getLogger(__filename);
  6. // If after 3000 ms the detector did not find any active devices consider that there aren't any usable ones available
  7. // i.e. audioLevel > 0.008
  8. const DETECTION_TIMEOUT = 3000;
  9. /**
  10. * Go through all audio devices on the system and return one that is active, i.e. has audio signal.
  11. *
  12. * @returns Promise<Object> - Object containing information about the found device.
  13. */
  14. export default function getActiveAudioDevice() {
  15. return new Promise(resolve => {
  16. RTC.enumerateDevices(devices => {
  17. const audioDevices = devices.filter(device => device.kind === 'audioinput');
  18. const devicePromiseArray = [];
  19. for (const micDevice of audioDevices) {
  20. const devicePromise = RTC.obtainAudioAndVideoPermissions({ devices: [ 'audio' ],
  21. micDeviceId: micDevice.deviceId }).then(tracks => {
  22. // We expect a single device to be available when obtained from obtainAudioAndVideoPermissions
  23. // that's why only take p.value[0].
  24. const track = tracks[0];
  25. Statistics.startLocalStats(track, track.setAudioLevel.bind(track));
  26. return track;
  27. });
  28. devicePromiseArray.push(devicePromise);
  29. }
  30. Promise.allSettled(devicePromiseArray).then(outcomeArray => {
  31. const successfulPromises = outcomeArray.filter(p => p.status === 'fulfilled');
  32. const rejectedPromises = outcomeArray.filter(p => p.status === 'rejected');
  33. const availableDevices = successfulPromises.map(p => p.value);
  34. const rejectReasons = rejectedPromises.map(p => p.value);
  35. for (const reason of rejectReasons) {
  36. logger.error('Failed to acquire audio device with error: ', reason);
  37. }
  38. // Setup event handlers for monitored devices.
  39. for (const device of availableDevices) {
  40. device.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevel => {
  41. // This is a very naive approach but works, a more accurate one would be to use rnnoise in
  42. // order to limit the number of false positives. The 0.008 constant is due to how
  43. // LocalStatsCollector from lib-jitsi-meet publishes audio-levels, in this case 0.008 denotes //
  44. // no input.
  45. if (audioLevel > 0.008) {
  46. stopActiveDevices(availableDevices);
  47. resolve({ deviceId: device.deviceId,
  48. deviceLabel: device.track.label });
  49. }
  50. });
  51. }
  52. // Cancel the detection in case no devices was found with audioLevel > 0 in the set timeout.
  53. setTimeout(() => {
  54. stopActiveDevices(availableDevices);
  55. resolve({
  56. deviceId: '',
  57. deviceLabel: '' }
  58. );
  59. }, DETECTION_TIMEOUT);
  60. });
  61. });
  62. });
  63. }
  64. /**
  65. * Stop the streams of the provided JitsiLocalTracks.
  66. *
  67. * @param {Array<JitsiLocalTrack>} deviceList - Array of JitsiLocalTracks to stop.
  68. * @returns {void}
  69. */
  70. function stopActiveDevices(deviceList) {
  71. for (const device of deviceList) {
  72. device.stopStream();
  73. }
  74. }