您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ActiveDeviceDetector.js 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  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. const originalStream = track.getOriginalStream();
  26. Statistics.startLocalStats(originalStream, track.setAudioLevel.bind(track));
  27. track.addEventListener(JitsiTrackEvents.LOCAL_TRACK_STOPPED, () => {
  28. Statistics.stopLocalStats(originalStream);
  29. });
  30. return track;
  31. });
  32. devicePromiseArray.push(devicePromise);
  33. }
  34. Promise.allSettled(devicePromiseArray).then(outcomeArray => {
  35. const successfulPromises = outcomeArray.filter(p => p.status === 'fulfilled');
  36. const rejectedPromises = outcomeArray.filter(p => p.status === 'rejected');
  37. const availableDevices = successfulPromises.map(p => p.value);
  38. const rejectReasons = rejectedPromises.map(p => p.value);
  39. for (const reason of rejectReasons) {
  40. logger.error('Failed to acquire audio device with error: ', reason);
  41. }
  42. // Setup event handlers for monitored devices.
  43. for (const device of availableDevices) {
  44. device.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevel => {
  45. // This is a very naive approach but works, a more accurate one would be to use rnnoise in
  46. // order to limit the number of false positives. The 0.008 constant is due to how
  47. // LocalStatsCollector from lib-jitsi-meet publishes audio-levels, in this case 0.008 denotes //
  48. // no input.
  49. if (audioLevel > 0.008) {
  50. stopActiveDevices(availableDevices);
  51. resolve({ deviceId: device.deviceId,
  52. deviceLabel: device.track.label });
  53. }
  54. });
  55. }
  56. // Cancel the detection in case no devices was found with audioLevel > 0 in the set timeout.
  57. setTimeout(() => {
  58. stopActiveDevices(availableDevices);
  59. resolve({
  60. deviceId: '',
  61. deviceLabel: '' }
  62. );
  63. }, DETECTION_TIMEOUT);
  64. });
  65. });
  66. });
  67. }
  68. /**
  69. * Stop the streams of the provided JitsiLocalTracks.
  70. *
  71. * @param {Array<JitsiLocalTrack>} deviceList - Array of JitsiLocalTracks to stop.
  72. * @returns {void}
  73. */
  74. function stopActiveDevices(deviceList) {
  75. for (const device of deviceList) {
  76. device.stopStream();
  77. }
  78. }