UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
258 lines (256 loc) 12.4 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. Object.defineProperty(exports, "__esModule", { value: true }); var AudioStreamFormat_1 = require("../../src/sdk/Audio/AudioStreamFormat"); var Exports_1 = require("../common.speech/Exports"); var Exports_2 = require("../common/Exports"); exports.AudioWorkletSourceURLPropertyName = "MICROPHONE-WorkletSourceUrl"; var MicAudioSource = /** @class */ (function () { function MicAudioSource(privRecorder, outputChunkSize, audioSourceId, deviceId) { var _this = this; this.privRecorder = privRecorder; this.deviceId = deviceId; this.privStreams = {}; this.turnOn = function () { if (_this.privInitializeDeferral) { return _this.privInitializeDeferral.promise(); } _this.privInitializeDeferral = new Exports_2.Deferred(); _this.createAudioContext(); var nav = window.navigator; var getUserMedia = (nav.getUserMedia || nav.webkitGetUserMedia || nav.mozGetUserMedia || nav.msGetUserMedia); if (!!nav.mediaDevices) { getUserMedia = function (constraints, successCallback, errorCallback) { nav.mediaDevices .getUserMedia(constraints) .then(successCallback) .catch(errorCallback); }; } if (!getUserMedia) { var errorMsg = "Browser does not support getUserMedia."; _this.privInitializeDeferral.reject(errorMsg); _this.onEvent(new Exports_2.AudioSourceErrorEvent(errorMsg, "")); // mic initialized error - no streamid at this point } else { var next = function () { _this.onEvent(new Exports_2.AudioSourceInitializingEvent(_this.privId)); // no stream id getUserMedia({ audio: _this.deviceId ? { deviceId: _this.deviceId } : true, video: false }, function (mediaStream) { _this.privMediaStream = mediaStream; _this.onEvent(new Exports_2.AudioSourceReadyEvent(_this.privId)); _this.privInitializeDeferral.resolve(true); }, function (error) { var errorMsg = "Error occurred during microphone initialization: " + error; var tmp = _this.privInitializeDeferral; // HACK: this should be handled through onError callbacks of all promises up the stack. // Unfortunately, the current implementation does not provide an easy way to reject promises // without a lot of code replication. // TODO: fix promise implementation, allow for a graceful reject chaining. _this.privInitializeDeferral = null; tmp.reject(errorMsg); // this will bubble up through the whole chain of promises, // with each new level adding extra "Unhandled callback error" prefix to the error message. // The following line is not guaranteed to be executed. _this.onEvent(new Exports_2.AudioSourceErrorEvent(_this.privId, errorMsg)); }); }; if (_this.privContext.state === "suspended") { // NOTE: On iOS, the Web Audio API requires sounds to be triggered from an explicit user action. // https://github.com/WebAudio/web-audio-api/issues/790 _this.privContext.resume().then(next, function (reason) { _this.privInitializeDeferral.reject("Failed to initialize audio context: " + reason); }); } else { next(); } } return _this.privInitializeDeferral.promise(); }; this.id = function () { return _this.privId; }; this.attach = function (audioNodeId) { _this.onEvent(new Exports_2.AudioStreamNodeAttachingEvent(_this.privId, audioNodeId)); return _this.listen(audioNodeId).onSuccessContinueWith(function (streamReader) { _this.onEvent(new Exports_2.AudioStreamNodeAttachedEvent(_this.privId, audioNodeId)); return { detach: function () { streamReader.close(); delete _this.privStreams[audioNodeId]; _this.onEvent(new Exports_2.AudioStreamNodeDetachedEvent(_this.privId, audioNodeId)); _this.turnOff(); }, id: function () { return audioNodeId; }, read: function () { return streamReader.read(); }, }; }); }; this.detach = function (audioNodeId) { if (audioNodeId && _this.privStreams[audioNodeId]) { _this.privStreams[audioNodeId].close(); delete _this.privStreams[audioNodeId]; _this.onEvent(new Exports_2.AudioStreamNodeDetachedEvent(_this.privId, audioNodeId)); } }; this.turnOff = function () { for (var streamId in _this.privStreams) { if (streamId) { var stream = _this.privStreams[streamId]; if (stream) { stream.close(); } } } _this.onEvent(new Exports_2.AudioSourceOffEvent(_this.privId)); // no stream now _this.privInitializeDeferral = null; _this.destroyAudioContext(); return Exports_2.PromiseHelper.fromResult(true); }; this.listen = function (audioNodeId) { return _this.turnOn() .onSuccessContinueWith(function (_) { var stream = new Exports_2.ChunkedArrayBufferStream(_this.privOutputChunkSize, audioNodeId); _this.privStreams[audioNodeId] = stream; try { _this.privRecorder.record(_this.privContext, _this.privMediaStream, stream); } catch (error) { _this.onEvent(new Exports_2.AudioStreamNodeErrorEvent(_this.privId, audioNodeId, error)); throw error; } return stream.getReader(); }); }; this.onEvent = function (event) { _this.privEvents.onEvent(event); Exports_2.Events.instance.onEvent(event); }; this.createAudioContext = function () { if (!!_this.privContext) { return; } // https://developer.mozilla.org/en-US/docs/Web/API/AudioContext var AudioContext = (window.AudioContext) || (window.webkitAudioContext) || false; if (!AudioContext) { throw new Error("Browser does not support Web Audio API (AudioContext is not available)."); } _this.privContext = new AudioContext(); }; this.destroyAudioContext = function () { if (!_this.privContext) { return; } _this.privRecorder.releaseMediaResources(_this.privContext); // This pattern brought to you by a bug in the TypeScript compiler where it // confuses the ("close" in this.privContext) with this.privContext always being null as the alternate. // https://github.com/Microsoft/TypeScript/issues/11498 var hasClose = false; if ("close" in _this.privContext) { hasClose = true; } if (hasClose) { _this.privContext.close(); _this.privContext = null; } else if (null !== _this.privContext && _this.privContext.state === "running") { // Suspend actually takes a callback, but analogous to the // resume method, it'll be only fired if suspend is called // in a direct response to a user action. The later is not always // the case, as TurnOff is also called, when we receive an // end-of-speech message from the service. So, doing a best effort // fire-and-forget here. _this.privContext.suspend(); } }; this.privOutputChunkSize = outputChunkSize; this.privId = audioSourceId ? audioSourceId : Exports_2.createNoDashGuid(); this.privEvents = new Exports_2.EventSource(); } Object.defineProperty(MicAudioSource.prototype, "format", { get: function () { return MicAudioSource.AUDIOFORMAT; }, enumerable: true, configurable: true }); Object.defineProperty(MicAudioSource.prototype, "events", { get: function () { return this.privEvents; }, enumerable: true, configurable: true }); Object.defineProperty(MicAudioSource.prototype, "deviceInfo", { get: function () { return this.getMicrophoneLabel().onSuccessContinueWith(function (label) { return { bitspersample: MicAudioSource.AUDIOFORMAT.bitsPerSample, channelcount: MicAudioSource.AUDIOFORMAT.channels, connectivity: Exports_1.connectivity.Unknown, manufacturer: "Speech SDK", model: label, samplerate: MicAudioSource.AUDIOFORMAT.samplesPerSec, type: Exports_1.type.Microphones, }; }); }, enumerable: true, configurable: true }); MicAudioSource.prototype.setProperty = function (name, value) { if (name === exports.AudioWorkletSourceURLPropertyName) { this.privRecorder.setWorkletUrl(value); } else { throw new Error("Property '" + name + "' is not supported on Microphone."); } }; MicAudioSource.prototype.getMicrophoneLabel = function () { var _this = this; var defaultMicrophoneName = "microphone"; // If we did this already, return the value. if (this.privMicrophoneLabel !== undefined) { return Exports_2.PromiseHelper.fromResult(this.privMicrophoneLabel); } // If the stream isn't currently running, we can't query devices because security. if (this.privMediaStream === undefined || !this.privMediaStream.active) { return Exports_2.PromiseHelper.fromResult(defaultMicrophoneName); } // Setup a default this.privMicrophoneLabel = defaultMicrophoneName; // Get the id of the device running the audio track. var microphoneDeviceId = this.privMediaStream.getTracks()[0].getSettings().deviceId; // If the browser doesn't support getting the device ID, set a default and return. if (undefined === microphoneDeviceId) { return Exports_2.PromiseHelper.fromResult(this.privMicrophoneLabel); } var deferred = new Exports_2.Deferred(); // Enumerate the media devices. navigator.mediaDevices.enumerateDevices().then(function (devices) { for (var _i = 0, devices_1 = devices; _i < devices_1.length; _i++) { var device = devices_1[_i]; if (device.deviceId === microphoneDeviceId) { // Found the device _this.privMicrophoneLabel = device.label; break; } } deferred.resolve(_this.privMicrophoneLabel); }, function () { return deferred.resolve(_this.privMicrophoneLabel); }); return deferred.promise(); }; MicAudioSource.AUDIOFORMAT = AudioStreamFormat_1.AudioStreamFormat.getDefaultInputFormat(); return MicAudioSource; }()); exports.MicAudioSource = MicAudioSource; //# sourceMappingURL=MicAudioSource.js.map