UNPKG

@twilio/voice-sdk

Version:
1,107 lines (1,080 loc) 555 kB
/*! @twilio/voice-sdk.js 2.7.1 The following license applies to all parts of this software except as documented below. Copyright (C) 2015-2023 Twilio, inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. This software includes rtcpeerconnection-shim under the following (BSD 3-Clause) license. Copyright (c) 2017 Philipp Hancke. All rights reserved. Copyright (c) 2014, The WebRTC project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Philipp Hancke nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software includes backoff under the following (MIT) license. Copyright (C) 2012 Mathieu Turcotte Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This software includes loglevel under the following (MIT) license. Copyright (c) 2013 Tim Perry Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function(root) { var bundle = (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwilioError = exports.Logger = exports.PreflightTest = exports.Device = exports.Call = void 0; /** * @packageDocumentation * @internalapi */ var call_1 = require("./twilio/call"); exports.Call = call_1.default; var device_1 = require("./twilio/device"); exports.Device = device_1.default; var TwilioError = require("./twilio/errors"); exports.TwilioError = TwilioError; var log_1 = require("./twilio/log"); Object.defineProperty(exports, "Logger", { enumerable: true, get: function () { return log_1.Logger; } }); var preflight_1 = require("./twilio/preflight/preflight"); Object.defineProperty(exports, "PreflightTest", { enumerable: true, get: function () { return preflight_1.PreflightTest; } }); },{"./twilio/call":8,"./twilio/device":11,"./twilio/errors":14,"./twilio/log":17,"./twilio/preflight/preflight":19}],2:[function(require,module,exports){ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsyncQueue = void 0; /** * @packageDocumentation * @module Voice * @internalapi */ var deferred_1 = require("./deferred"); /** * Queue async operations and executes them synchronously. */ var AsyncQueue = /** @class */ (function () { function AsyncQueue() { /** * The list of async operations in this queue */ this._operations = []; } /** * Adds the async operation to the queue * @param callback An async callback that returns a promise * @returns A promise that will get resolved or rejected after executing the callback */ AsyncQueue.prototype.enqueue = function (callback) { var hasPending = !!this._operations.length; var deferred = new deferred_1.default(); this._operations.push({ deferred: deferred, callback: callback }); if (!hasPending) { this._processQueue(); } return deferred.promise; }; /** * Start processing the queue. This executes the first item and removes it after. * Then do the same for next items until the queue is emptied. */ AsyncQueue.prototype._processQueue = function () { return __awaiter(this, void 0, void 0, function () { var _a, deferred, callback, result, error, hasResolved, e_1; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!this._operations.length) return [3 /*break*/, 5]; _a = this._operations[0], deferred = _a.deferred, callback = _a.callback; result = void 0; error = void 0; hasResolved = void 0; _b.label = 1; case 1: _b.trys.push([1, 3, , 4]); return [4 /*yield*/, callback()]; case 2: result = _b.sent(); hasResolved = true; return [3 /*break*/, 4]; case 3: e_1 = _b.sent(); error = e_1; return [3 /*break*/, 4]; case 4: // Remove the item this._operations.shift(); if (hasResolved) { deferred.resolve(result); } else { deferred.reject(error); } return [3 /*break*/, 0]; case 5: return [2 /*return*/]; } }); }); }; return AsyncQueue; }()); exports.AsyncQueue = AsyncQueue; },{"./deferred":10}],3:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); /** * @packageDocumentation * @module Voice */ var events_1 = require("events"); var device_1 = require("./device"); var errors_1 = require("./errors"); var log_1 = require("./log"); var outputdevicecollection_1 = require("./outputdevicecollection"); var mediadeviceinfo_1 = require("./shims/mediadeviceinfo"); var mediadevices_1 = require("./shims/mediadevices"); var util_1 = require("./util"); /** * Aliases for audio kinds, used for labelling. * @private */ var kindAliases = { audioinput: 'Audio Input', audiooutput: 'Audio Output', }; /** * Provides input and output audio-based functionality in one convenient class. * @publicapi */ var AudioHelper = /** @class */ (function (_super) { __extends(AudioHelper, _super); /** * @constructor * @private * @param onActiveOutputsChanged - A callback to be called when the user changes the active output devices. * @param onActiveInputChanged - A callback to be called when the user changes the active input device. * @param getUserMedia - The getUserMedia method to use. * @param [options] */ function AudioHelper(onActiveOutputsChanged, onActiveInputChanged, getUserMedia, options) { var _a; var _this = _super.call(this) || this; /** * A Map of all audio input devices currently available to the browser by their device ID. */ _this.availableInputDevices = new Map(); /** * A Map of all audio output devices currently available to the browser by their device ID. */ _this.availableOutputDevices = new Map(); /** * The currently set audio constraints set by setAudioConstraints(). */ _this._audioConstraints = null; /** * Whether each sound is enabled. */ _this._enabledSounds = (_a = {}, _a[device_1.default.SoundName.Disconnect] = true, _a[device_1.default.SoundName.Incoming] = true, _a[device_1.default.SoundName.Outgoing] = true, _a); /** * The current input device. */ _this._inputDevice = null; /** * The current input stream. */ _this._inputStream = null; /** * Whether the {@link AudioHelper} is currently polling the input stream's volume. */ _this._isPollingInputVolume = false; /** * An instance of Logger to use. */ _this._log = log_1.default.getInstance(); /** * A record of unknown devices (Devices without labels) */ _this._unknownDeviceIndexes = { audioinput: {}, audiooutput: {}, }; /** * Remove an input device from inputs * @param lostDevice * @returns Whether the device was active */ _this._removeLostInput = function (lostDevice) { if (!_this.inputDevice || _this.inputDevice.deviceId !== lostDevice.deviceId) { return false; } _this._replaceStream(null); _this._inputDevice = null; _this._maybeStopPollingVolume(); var defaultDevice = _this.availableInputDevices.get('default') || Array.from(_this.availableInputDevices.values())[0]; if (defaultDevice) { _this.setInputDevice(defaultDevice.deviceId); } return true; }; /** * Remove an input device from outputs * @param lostDevice * @returns Whether the device was active */ _this._removeLostOutput = function (lostDevice) { var wasSpeakerLost = _this.speakerDevices.delete(lostDevice); var wasRingtoneLost = _this.ringtoneDevices.delete(lostDevice); return wasSpeakerLost || wasRingtoneLost; }; /** * Update the available input and output devices */ _this._updateAvailableDevices = function () { if (!_this._mediaDevices || !_this._enumerateDevices) { return Promise.reject('Enumeration not supported'); } return _this._enumerateDevices().then(function (devices) { _this._updateDevices(devices.filter(function (d) { return d.kind === 'audiooutput'; }), _this.availableOutputDevices, _this._removeLostOutput); _this._updateDevices(devices.filter(function (d) { return d.kind === 'audioinput'; }), _this.availableInputDevices, _this._removeLostInput); var defaultDevice = _this.availableOutputDevices.get('default') || Array.from(_this.availableOutputDevices.values())[0]; [_this.speakerDevices, _this.ringtoneDevices].forEach(function (outputDevices) { if (!outputDevices.get().size && _this.availableOutputDevices.size && _this.isOutputSelectionSupported) { outputDevices.set(defaultDevice.deviceId) .catch(function (reason) { _this._log.warn("Unable to set audio output devices. " + reason); }); } }); }); }; options = Object.assign({ AudioContext: typeof AudioContext !== 'undefined' && AudioContext, setSinkId: typeof HTMLAudioElement !== 'undefined' && HTMLAudioElement.prototype.setSinkId, }, options); _this._getUserMedia = getUserMedia; _this._mediaDevices = options.mediaDevices || mediadevices_1.default(); _this._onActiveInputChanged = onActiveInputChanged; _this._enumerateDevices = typeof options.enumerateDevices === 'function' ? options.enumerateDevices : _this._mediaDevices && _this._mediaDevices.enumerateDevices; var isAudioContextSupported = !!(options.AudioContext || options.audioContext); var isEnumerationSupported = !!_this._enumerateDevices; if (options.enabledSounds) { _this._enabledSounds = options.enabledSounds; } var isSetSinkSupported = typeof options.setSinkId === 'function'; _this.isOutputSelectionSupported = isEnumerationSupported && isSetSinkSupported; _this.isVolumeSupported = isAudioContextSupported; if (_this.isVolumeSupported) { _this._audioContext = options.audioContext || options.AudioContext && new options.AudioContext(); if (_this._audioContext) { _this._inputVolumeAnalyser = _this._audioContext.createAnalyser(); _this._inputVolumeAnalyser.fftSize = 32; _this._inputVolumeAnalyser.smoothingTimeConstant = 0.3; } } _this.ringtoneDevices = new outputdevicecollection_1.default('ringtone', _this.availableOutputDevices, onActiveOutputsChanged, _this.isOutputSelectionSupported); _this.speakerDevices = new outputdevicecollection_1.default('speaker', _this.availableOutputDevices, onActiveOutputsChanged, _this.isOutputSelectionSupported); _this.addListener('newListener', function (eventName) { if (eventName === 'inputVolume') { _this._maybeStartPollingVolume(); } }); _this.addListener('removeListener', function (eventName) { if (eventName === 'inputVolume') { _this._maybeStopPollingVolume(); } }); _this.once('newListener', function () { // NOTE (rrowland): Ideally we would only check isEnumerationSupported here, but // in at least one browser version (Tested in FF48) enumerateDevices actually // returns bad data for the listed devices. Instead, we check for // isOutputSelectionSupported to avoid these quirks that may negatively affect customers. if (!_this.isOutputSelectionSupported) { _this._log.warn('Warning: This browser does not support audio output selection.'); } if (!_this.isVolumeSupported) { _this._log.warn("Warning: This browser does not support Twilio's volume indicator feature."); } }); if (isEnumerationSupported) { _this._initializeEnumeration(); } return _this; } Object.defineProperty(AudioHelper.prototype, "audioConstraints", { /** * The currently set audio constraints set by setAudioConstraints(). Starts as null. */ get: function () { return this._audioConstraints; }, enumerable: false, configurable: true }); Object.defineProperty(AudioHelper.prototype, "inputDevice", { /** * The active input device. Having no inputDevice specified by `setInputDevice()` * will disable input selection related functionality. */ get: function () { return this._inputDevice; }, enumerable: false, configurable: true }); Object.defineProperty(AudioHelper.prototype, "inputStream", { /** * The current input stream. */ get: function () { return this._inputStream; }, enumerable: false, configurable: true }); /** * Current state of the enabled sounds * @private */ AudioHelper.prototype._getEnabledSounds = function () { return this._enabledSounds; }; /** * Start polling volume if it's supported and there's an input stream to poll. * @private */ AudioHelper.prototype._maybeStartPollingVolume = function () { var _this = this; if (!this.isVolumeSupported || !this._inputStream) { return; } this._updateVolumeSource(); if (this._isPollingInputVolume || !this._inputVolumeAnalyser) { return; } var bufferLength = this._inputVolumeAnalyser.frequencyBinCount; var buffer = new Uint8Array(bufferLength); this._isPollingInputVolume = true; var emitVolume = function () { if (!_this._isPollingInputVolume) { return; } if (_this._inputVolumeAnalyser) { _this._inputVolumeAnalyser.getByteFrequencyData(buffer); var inputVolume = util_1.average(buffer); _this.emit('inputVolume', inputVolume / 255); } requestAnimationFrame(emitVolume); }; requestAnimationFrame(emitVolume); }; /** * Stop polling volume if it's currently polling and there are no listeners. * @private */ AudioHelper.prototype._maybeStopPollingVolume = function () { if (!this.isVolumeSupported) { return; } if (!this._isPollingInputVolume || (this._inputStream && this.listenerCount('inputVolume'))) { return; } if (this._inputVolumeSource) { this._inputVolumeSource.disconnect(); delete this._inputVolumeSource; } this._isPollingInputVolume = false; }; /** * Unbind the listeners from mediaDevices. * @private */ AudioHelper.prototype._unbind = function () { if (!this._mediaDevices || !this._enumerateDevices) { throw new errors_1.NotSupportedError('Enumeration is not supported'); } if (this._mediaDevices.removeEventListener) { this._mediaDevices.removeEventListener('devicechange', this._updateAvailableDevices); this._mediaDevices.removeEventListener('deviceinfochange', this._updateAvailableDevices); } }; /** * Enable or disable the disconnect sound. * @param doEnable Passing `true` will enable the sound and `false` will disable the sound. * Not passing this parameter will not alter the enable-status of the sound. * @returns The enable-status of the sound. */ AudioHelper.prototype.disconnect = function (doEnable) { return this._maybeEnableSound(device_1.default.SoundName.Disconnect, doEnable); }; /** * Enable or disable the incoming sound. * @param doEnable Passing `true` will enable the sound and `false` will disable the sound. * Not passing this parameter will not alter the enable-status of the sound. * @returns The enable-status of the sound. */ AudioHelper.prototype.incoming = function (doEnable) { return this._maybeEnableSound(device_1.default.SoundName.Incoming, doEnable); }; /** * Enable or disable the outgoing sound. * @param doEnable Passing `true` will enable the sound and `false` will disable the sound. * Not passing this parameter will not alter the enable-status of the sound. * @returns The enable-status of the sound. */ AudioHelper.prototype.outgoing = function (doEnable) { return this._maybeEnableSound(device_1.default.SoundName.Outgoing, doEnable); }; /** * Set the MediaTrackConstraints to be applied on every getUserMedia call for new input * device audio. Any deviceId specified here will be ignored. Instead, device IDs should * be specified using {@link AudioHelper#setInputDevice}. The returned Promise resolves * when the media is successfully reacquired, or immediately if no input device is set. * @param audioConstraints - The MediaTrackConstraints to apply. */ AudioHelper.prototype.setAudioConstraints = function (audioConstraints) { this._audioConstraints = Object.assign({}, audioConstraints); delete this._audioConstraints.deviceId; return this.inputDevice ? this._setInputDevice(this.inputDevice.deviceId, true) : Promise.resolve(); }; /** * Replace the current input device with a new device by ID. * @param deviceId - An ID of a device to replace the existing * input device with. */ AudioHelper.prototype.setInputDevice = function (deviceId) { return !util_1.isFirefox() ? this._setInputDevice(deviceId, false) : Promise.reject(new errors_1.NotSupportedError('Firefox does not currently support opening multiple ' + 'audio input tracks simultaneously, even across different tabs. As a result, ' + 'Device.audio.setInputDevice is disabled on Firefox until support is added.\n' + 'Related BugZilla thread: https://bugzilla.mozilla.org/show_bug.cgi?id=1299324')); }; /** * Unset the MediaTrackConstraints to be applied on every getUserMedia call for new input * device audio. The returned Promise resolves when the media is successfully reacquired, * or immediately if no input device is set. */ AudioHelper.prototype.unsetAudioConstraints = function () { this._audioConstraints = null; return this.inputDevice ? this._setInputDevice(this.inputDevice.deviceId, true) : Promise.resolve(); }; /** * Unset the input device, stopping the tracks. This should only be called when not in a connection, and * will not allow removal of the input device during a live call. */ AudioHelper.prototype.unsetInputDevice = function () { var _this = this; if (!this.inputDevice) { return Promise.resolve(); } return this._onActiveInputChanged(null).then(function () { _this._replaceStream(null); _this._inputDevice = null; _this._maybeStopPollingVolume(); }); }; /** * Get the index of an un-labeled Device. * @param mediaDeviceInfo * @returns The index of the passed MediaDeviceInfo */ AudioHelper.prototype._getUnknownDeviceIndex = function (mediaDeviceInfo) { var id = mediaDeviceInfo.deviceId; var kind = mediaDeviceInfo.kind; var index = this._unknownDeviceIndexes[kind][id]; if (!index) { index = Object.keys(this._unknownDeviceIndexes[kind]).length + 1; this._unknownDeviceIndexes[kind][id] = index; } return index; }; /** * Initialize output device enumeration. */ AudioHelper.prototype._initializeEnumeration = function () { var _this = this; if (!this._mediaDevices || !this._enumerateDevices) { throw new errors_1.NotSupportedError('Enumeration is not supported'); } if (this._mediaDevices.addEventListener) { this._mediaDevices.addEventListener('devicechange', this._updateAvailableDevices); this._mediaDevices.addEventListener('deviceinfochange', this._updateAvailableDevices); } this._updateAvailableDevices().then(function () { if (!_this.isOutputSelectionSupported) { return; } Promise.all([ _this.speakerDevices.set('default'), _this.ringtoneDevices.set('default'), ]).catch(function (reason) { _this._log.warn("Warning: Unable to set audio output devices. " + reason); }); }); }; /** * Set whether the sound is enabled or not * @param soundName * @param doEnable * @returns Whether the sound is enabled or not */ AudioHelper.prototype._maybeEnableSound = function (soundName, doEnable) { if (typeof doEnable !== 'undefined') { this._enabledSounds[soundName] = doEnable; } return this._enabledSounds[soundName]; }; /** * Stop the tracks on the current input stream before replacing it with the passed stream. * @param stream - The new stream */ AudioHelper.prototype._replaceStream = function (stream) { if (this._inputStream) { this._inputStream.getTracks().forEach(function (track) { track.stop(); }); } this._inputStream = stream; }; /** * Replace the current input device with a new device by ID. * @param deviceId - An ID of a device to replace the existing * input device with. * @param forceGetUserMedia - If true, getUserMedia will be called even if * the specified device is already active. */ AudioHelper.prototype._setInputDevice = function (deviceId, forceGetUserMedia) { var _this = this; if (typeof deviceId !== 'string') { return Promise.reject(new errors_1.InvalidArgumentError('Must specify the device to set')); } var device = this.availableInputDevices.get(deviceId); if (!device) { return Promise.reject(new errors_1.InvalidArgumentError("Device not found: " + deviceId)); } if (this._inputDevice && this._inputDevice.deviceId === deviceId && this._inputStream) { if (!forceGetUserMedia) { return Promise.resolve(); } // If the currently active track is still in readyState `live`, gUM may return the same track // rather than returning a fresh track. this._inputStream.getTracks().forEach(function (track) { track.stop(); }); } var constraints = { audio: Object.assign({ deviceId: { exact: deviceId } }, this.audioConstraints) }; return this._getUserMedia(constraints).then(function (stream) { return _this._onActiveInputChanged(stream).then(function () { _this._replaceStream(stream); _this._inputDevice = device; _this._maybeStartPollingVolume(); }); }); }; /** * Update a set of devices. * @param updatedDevices - An updated list of available Devices * @param availableDevices - The previous list of available Devices * @param removeLostDevice - The method to call if a previously available Device is * no longer available. */ AudioHelper.prototype._updateDevices = function (updatedDevices, availableDevices, removeLostDevice) { var _this = this; var updatedDeviceIds = updatedDevices.map(function (d) { return d.deviceId; }); var knownDeviceIds = Array.from(availableDevices.values()).map(function (d) { return d.deviceId; }); var lostActiveDevices = []; // Remove lost devices var lostDeviceIds = util_1.difference(knownDeviceIds, updatedDeviceIds); lostDeviceIds.forEach(function (lostDeviceId) { var lostDevice = availableDevices.get(lostDeviceId); if (lostDevice) { availableDevices.delete(lostDeviceId); if (removeLostDevice(lostDevice)) { lostActiveDevices.push(lostDevice); } } }); // Add any new devices, or devices with updated labels var deviceChanged = false; updatedDevices.forEach(function (newDevice) { var existingDevice = availableDevices.get(newDevice.deviceId); var newMediaDeviceInfo = _this._wrapMediaDeviceInfo(newDevice); if (!existingDevice || existingDevice.label !== newMediaDeviceInfo.label) { availableDevices.set(newDevice.deviceId, newMediaDeviceInfo); deviceChanged = true; } }); if (deviceChanged || lostDeviceIds.length) { // Force a new gUM in case the underlying tracks of the active stream have changed. One // reason this might happen is when `default` is selected and set to a USB device, // then that device is unplugged or plugged back in. We can't check for the 'ended' // event or readyState because it is asynchronous and may take upwards of 5 seconds, // in my testing. (rrowland) if (this.inputDevice !== null && this.inputDevice.deviceId === 'default') { this._log.warn("Calling getUserMedia after device change to ensure that the tracks of the active device (default) have not gone stale."); this._setInputDevice(this.inputDevice.deviceId, true); } this.emit('deviceChange', lostActiveDevices); } }; /** * Disconnect the old input volume source, and create and connect a new one with the current * input stream. */ AudioHelper.prototype._updateVolumeSource = function () { if (!this._inputStream || !this._audioContext || !this._inputVolumeAnalyser) { return; } if (this._inputVolumeSource) { this._inputVolumeSource.disconnect(); } try { this._inputVolumeSource = this._audioContext.createMediaStreamSource(this._inputStream); this._inputVolumeSource.connect(this._inputVolumeAnalyser); } catch (ex) { this._log.warn('Unable to update volume source', ex); delete this._inputVolumeSource; } }; /** * Convert a MediaDeviceInfo to a IMediaDeviceInfoShim. * @param mediaDeviceInfo - The info to convert * @returns The converted shim */ AudioHelper.prototype._wrapMediaDeviceInfo = function (mediaDeviceInfo) { var options = { deviceId: mediaDeviceInfo.deviceId, groupId: mediaDeviceInfo.groupId, kind: mediaDeviceInfo.kind, label: mediaDeviceInfo.label, }; if (!options.label) { if (options.deviceId === 'default') { options.label = 'Default'; } else { var index = this._getUnknownDeviceIndex(mediaDeviceInfo); options.label = "Unknown " + kindAliases[options.kind] + " Device " + index; } } return new mediadeviceinfo_1.default(options); }; return AudioHelper; }(events_1.EventEmitter)); (function (AudioHelper) { })(AudioHelper || (AudioHelper = {})); exports.default = AudioHelper; },{"./device":11,"./errors":14,"./log":17,"./outputdevicecollection":18,"./shims/mediadeviceinfo":33,"./shims/mediadevices":34,"./util":37,"events":40}],4:[function(require,module,exports){ "use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); /** * @packageDocumentation * @module Voice * @internalapi */ // @ts-nocheck var deferred_1 = require("./deferred"); var eventtarget_1 = require("./eventtarget"); /** * An {@link AudioPlayer} is an HTMLAudioElement-like object that uses AudioContext * to circumvent browser limitations. * @private */ var AudioPlayer = /** @class */ (function (_super) { __extends(AudioPlayer, _super); /** * @private */ function AudioPlayer(audioContext, srcOrOptions, options) { if (srcOrOptions === void 0) { srcOrOptions = {}; } if (options === void 0) { options = {}; } var _this = _super.call(this) || this; /** * The AudioBufferSourceNode of the actively loaded sound. Null if a sound * has not been loaded yet. This is re-used for each time the sound is * played. */ _this._audioNode = null; /** * Whether or not the audio element should loop. If disabled during playback, * playing continues until the sound ends and then stops looping. */ _this._loop = false; /** * An Array of deferred-like objects for each pending `play` Promise. When * .pause() is called or .src is set, all pending play Promises are * immediately rejected. */ _this._pendingPlayDeferreds = []; /** * The current sinkId of the device audio is being played through. */ _this._sinkId = 'default'; /** * The source URL of the sound to play. When set, the currently playing sound will stop. */ _this._src = ''; if (typeof srcOrOptions !== 'string') { options = srcOrOptions; } _this._audioContext = audioContext; _this._audioElement = new (options.AudioFactory || Audio)(); _this._bufferPromise = _this._createPlayDeferred().promise; _this._destination = _this._audioContext.destination; _this._gainNode = _this._audioContext.createGain(); _this._gainNode.connect(_this._destination); _this._XMLHttpRequest = options.XMLHttpRequestFactory || XMLHttpRequest; _this.addEventListener('canplaythrough', function () { _this._resolvePlayDeferreds(); }); if (typeof srcOrOptions === 'string') { _this.src = srcOrOptions; } return _this; } Object.defineProperty(AudioPlayer.prototype, "destination", { get: function () { return this._destination; }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "loop", { get: function () { return this._loop; }, set: function (shouldLoop) { var self = this; function pauseAfterPlaythrough() { self._audioNode.removeEventListener('ended', pauseAfterPlaythrough); self.pause(); } // If a sound is already looping, it should continue playing // the current playthrough and then stop. if (!shouldLoop && this.loop && !this.paused) { this._audioNode.addEventListener('ended', pauseAfterPlaythrough); } this._loop = shouldLoop; }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "muted", { /** * Whether the audio element is muted. */ get: function () { return this._gainNode.gain.value === 0; }, set: function (shouldBeMuted) { this._gainNode.gain.value = shouldBeMuted ? 0 : 1; }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "paused", { /** * Whether the sound is paused. this._audioNode only exists when sound is playing; * otherwise AudioPlayer is considered paused. */ get: function () { return this._audioNode === null; }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "src", { get: function () { return this._src; }, set: function (src) { this._load(src); }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "srcObject", { /** * The srcObject of the HTMLMediaElement */ get: function () { return this._audioElement.srcObject; }, set: function (srcObject) { this._audioElement.srcObject = srcObject; }, enumerable: false, configurable: true }); Object.defineProperty(AudioPlayer.prototype, "sinkId", { get: function () { return this._sinkId; }, enumerable: false, configurable: true }); /** * Stop any ongoing playback and reload the source file. */ AudioPlayer.prototype.load = function () { this._load(this._src); }; /** * Pause the audio coming from this AudioPlayer. This will reject any pending * play Promises. */ AudioPlayer.prototype.pause = function () { if (this.paused) { return; } this._audioElement.pause(); this._audioNode.stop(); this._audioNode.disconnect(this._gainNode); this._audioNode = null; this._rejectPlayDeferreds(new Error('The play() request was interrupted by a call to pause().')); }; /** * Play the sound. If the buffer hasn't loaded yet, wait for the buffer to load. If * the source URL is not set yet, this Promise will remain pending until a source * URL is set. */ AudioPlayer.prototype.play = function () { return __awaiter(this, void 0, void 0, function () { var buffer; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!!this.paused) return [3 /*break*/, 2]; return [4 /*yield*/, this._bufferPromise]; case 1: _a.sent(); if (!this.paused) { return [2 /*return*/]; } throw new Error('The play() request was interrupted by a call to pause().'); case 2: this._audioNode = this._audioContext.createBufferSource(); this._audioNode.loop = this.loop; this._audioNode.addEventListener('ended', function () { if (_this._audioNode && _this._audioNode.loop) { return; } _this.dispatchEvent('ended'); }); return [4 /*yield*/, this._bufferPromise]; case 3: buffer = _a.sent(); if (this.paused) { throw new Error('The play() request was interrupted by a call to pause().'); } this._audioNode.buffer = buffer; this._audioNode.connect(this._gainNode); this._audioNode.start(); if (this._audioElement.srcObject) { return [2 /*return*/, this._audioElement.play()]; } return [2 /*return*/]; } }); }); }; /** * Change which device the sound should play through. * @param sinkId - The sink of the device to play sound through. */ AudioPlayer.prototype.setSinkId = function (sinkId) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (typeof this._audioElement.setSinkId !== 'function') { throw new Error('This browser does not support setSinkId.'); } if (sinkId === this.sinkId) { return [2 /*return*/]; } if (sinkId === 'default') { if (!this.paused) { this._gainNode.disconnect(this._destination); } this._audioElement.srcObject = null; this._destination = this._audioContext.destination; this._gainNode.connect(this._destination); this._sinkId = sinkId; return [2 /*return*/]; } return [4 /*yield*/, this._audioElement.setSinkId(sinkId)]; case 1: _a.sent(); if (this._audioElement.srcObject) { return [2 /*return*/]; } this._gainNode.disconnect(this._audioContext.destination); this._destination = this._audioContext.createMediaStreamDestination(); this._audioElement.srcObject = this._destination.stream; this._sinkId = sinkId; this._gainNode.connect(this._destination); return [2 /*return*/]; } }); }); }; /** * Create a Deferred for a Promise that will be resolved when .src is set or rejected * when .pause is called. */ AudioPlayer.prototype._createPlayDeferred = function () { var deferred = new deferred_1.default(); this._pendingPlayDeferreds.push(deferred); return deferred; }; /** * Stop current playback and load a sound file. * @param src - The source URL of the file to load */ AudioPlayer.prototyp