123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- // @flow
- import EventEmitter from 'events';
- import { ACTIVE_DEVICE_DETECTED } from './Events';
- import logger from '../../logger';
- import JitsiMeetJS from '../../../lib-jitsi-meet';
-
- const JitsiTrackEvents = JitsiMeetJS.events.track;
-
- // If after 3000 ms the detector did not find any active devices consider that there aren't any usable ones available
- // i.e. audioLevel > 0.008
- const DETECTION_TIMEOUT = 3000;
-
- /**
- * Detect active input devices based on their audio levels, currently this is very simplistic. It works by simply
- * checking all monitored devices for TRACK_AUDIO_LEVEL_CHANGED if a device has a audio level > 0.008 ( 0.008 is
- * no input from the perspective of a JitsiLocalTrack ), at which point it triggers a ACTIVE_DEVICE_DETECTED event.
- * If there are no devices that meet that criteria for DETECTION_TIMEOUT an event with empty deviceLabel parameter
- * will be triggered,
- * signaling that no active device was detected.
- * TODO Potentially improve the active device detection using rnnoise VAD scoring.
- */
- export class ActiveDeviceDetector extends EventEmitter {
-
- /**
- * Currently monitored devices.
- */
- _availableDevices: Array<Object>;
-
- /**
- * State flag, check if the instance was destroyed.
- */
- _destroyed: boolean = false;
-
- /**
- * Create active device detector.
- *
- * @param {Array<MediaDeviceInfo>} micDeviceList - Device list that is monitored inside the service.
- *
- * @returns {ActiveDeviceDetector}
- */
- static async create(micDeviceList: Array<MediaDeviceInfo>) {
- const availableDevices = [];
-
- try {
- for (const micDevice of micDeviceList) {
- const localTrack = await JitsiMeetJS.createLocalTracks({
- devices: [ 'audio' ],
- micDeviceId: micDevice.deviceId
- });
-
- // We provide a specific deviceId thus we expect a single JitsiLocalTrack to be returned.
- availableDevices.push(localTrack[0]);
- }
-
- return new ActiveDeviceDetector(availableDevices);
- } catch (error) {
- logger.error('Cleaning up remaining JitsiLocalTrack, due to ActiveDeviceDetector create fail!');
-
- for (const device of availableDevices) {
- device.stopStream();
- }
-
- throw error;
- }
- }
-
- /**
- * Constructor.
- *
- * @param {Array<Object>} availableDevices - Device list that is monitored inside the service.
- */
- constructor(availableDevices: Array<Object>) {
- super();
-
- this._availableDevices = availableDevices;
-
- // Setup event handlers for monitored devices.
- for (const device of this._availableDevices) {
- device.on(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevel => {
- this._handleAudioLevelEvent(device, audioLevel);
- });
- }
-
- // Cancel the detection in case no devices was found with audioLevel > 0 in te set timeout.
- setTimeout(this._handleDetectionTimeout.bind(this), DETECTION_TIMEOUT);
- }
-
- /**
- * Handle what happens if no device publishes a score in the defined time frame, i.e. Emit an event with empty
- * deviceLabel.
- *
- * @returns {void}
- */
- _handleDetectionTimeout() {
- if (!this._destroyed) {
- this.emit(ACTIVE_DEVICE_DETECTED, { deviceLabel: '',
- audioLevel: 0 });
- this.destroy();
- }
- }
-
- /**
- * Handles audio level event generated by JitsiLocalTracks.
- *
- * @param {Object} device - Label of the emitting track.
- * @param {number} audioLevel - Audio level generated by device.
- *
- * @returns {void}
- */
- _handleAudioLevelEvent(device, audioLevel) {
- if (!this._destroyed) {
- // This is a very naive approach but works is most, a more accurate approach would ne to use rnnoise
- // in order to limit the number of false positives.
- // The 0.008 constant is due to how LocalStatsCollector from lib-jitsi-meet publishes audio-levels, in this
- // case 0.008 denotes no input.
- // TODO potentially refactor lib-jitsi-meet to expose this constant as a function. i.e. getSilenceLevel.
- if (audioLevel > 0.008) {
- this.emit(ACTIVE_DEVICE_DETECTED, { deviceId: device.deviceId,
- deviceLabel: device.track.label,
- audioLevel });
- this.destroy();
- }
- }
- }
-
- /**
- * Destroy the ActiveDeviceDetector, clean up the currently monitored devices associated JitsiLocalTracks.
- *
- * @returns {void}.
- */
- destroy() {
- if (this._destroyed) {
- return;
- }
-
- for (const device of this._availableDevices) {
- device.removeAllListeners();
- device.stopStream();
- }
-
- this._destroyed = true;
- }
- }
|