UNPKG

decibel-meter

Version:

Decibel meter for detecting noise levels from microphone via Web Audio API

230 lines (160 loc) 5.47 kB
import uuid from 'uuid' import PubSub from 'PubSub' import DecibelAnalyser from 'DecibelAnalyser' import { registry, register, getById } from 'registry' export default class DecibelMeter { constructor(id = uuid()) { this.id = id this.listening = false this.events = new PubSub(this) this._connection = null register(this) } get source() { return this._connection && this._connection.source ? this._connection.source : null } get connection() { return this._connection } get sources() { if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) return navigator.mediaDevices.enumerateDevices().then(devices => devices.filter(device => device.kind === 'audioinput')) else return Promise.reject(new Error('MediaDevices.enumerateDevices() is not supported')) } get connected() { return this._connection !== null } on(eventName, callback) { this.events.on(eventName, callback) return callback } off(eventName = null, callback = null) { this.events.off(eventName, callback) return callback } listen() { if (!this.listening) { this.listening = true this.events.dispatch('change', this.listening) if (this.connection) this.connection.dBAnalyser.start() } return this } stopListening() { if (this.listening) { if (this.connection) this.connection.dBAnalyser.stop() this.listening = false this.events.dispatch('change', this.listening) } return this } listenTo(identifier, callback) { return this.connectTo(identifier).then(meter => { this.events.on('sample', callback) meter.listen() return this }) } connect(source) { if (!(source instanceof MediaDeviceInfo)) return Promise.reject(new TypeError('DecibelMeter: Expected first parameter to be of type MediaDeviceInfo')) else { // connection exists, and already connection to requested source if (this._connection && this._connection.source === source) return Promise.resolve(this) // audio source constraints const constraints = { audio: { mandatory: { sourceId: source.deviceId // force specific selected device } } } // connect return navigator.mediaDevices.getUserMedia(constraints).then(stream => { let previous = null, connection // already have a connection? if (this._connection) { // note previous source previous = this._connection.source // we already have a connection, but to a different source const oldSource = this._connection.source const oldStream = this._connection.stream // disconnect track for old source, if connected to stream const trackToDiscard = oldStream.getAudioTracks().find(track => track.label === oldSource.label) if (trackToDiscard) { trackToDiscard.stop() oldStream.removeTrack(trackToDiscard) } // stop analyzing dB this._connection.dBAnalyser.stop() // disconnect stream source this._connection.streamSource.disconnect() // discard old stream delete this._connection.stream delete this._connection.streamSource // set new properties connection = this._connection connection.source = source connection.stream = stream connection.streamSource = connection.context.createMediaStreamSource(stream) connection.streamSource.connect(connection.analyser) } else { // first time connecting connection = { stream, source, context: new AudioContext } connection.analyser = connection.context.createAnalyser() connection.analyser.smoothingTimeConstant = .5 connection.lastSample = new Uint8Array(1) connection.streamSource = connection.context.createMediaStreamSource(stream) connection.streamSource.connect(connection.analyser) connection.dBAnalyser = new DecibelAnalyser(this) this._connection = connection } this.events.dispatch('connect', source, previous) // listening may have started prior to connecting, or source changed if (this.listening && !connection.dBAnalyser.running) connection.dBAnalyser.start() return this // finally, resolve with DecibelMeter itself }) } } connectTo(identifier) { return this.sources.then(sources => { if (sources.length === 0) throw new ReferenceError('DecibelMeter: No audioinput sources available') if (typeof identifier === 'number') return sources[identifier] ? this.connect(sources[identifier]) : Promise.reject(new ReferenceError(`DecibelMeter: Source not found INDEX ${identifier}`)) if (typeof identifier === 'string') { const source = sources.find(source => source.deviceId === identifier) return source ? this.connect(source) : Promise.reject(new ReferenceError(`DecibelMeter: Source not found DEVICEID ${identifier}`)) } return Promise.reject(new TypeError('DecibelMeter: Invalid source identifier')) }) } disconnect() { this.stopListening() if (this.connection) { this.connection.dBAnalyser.stop() this.connection.streamSource.disconnect() this.connection.stream.getAudioTracks().forEach(t => t.stop()) } return new Promise(resolve => { setTimeout(() => { delete this._connection.stream delete this._connection.streamSource this._connection = null this.events.dispatch('disconnect') resolve(this) }, 100) }) } static getMeterById(id) { return getById(id) } static get meters() { return registry() } }