UNPKG

twilio-video

Version:

Twilio Video JavaScript Library

347 lines 18.2 kB
'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) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var isIOS = require('../../util/browserdetection').isIOS; var detectSilentAudio = require('../../util/detectsilentaudio'); var isIOSChrome = require('../../webrtc/util').isIOSChrome; var AudioTrack = require('./audiotrack'); var mixinLocalMediaTrack = require('./localmediatrack'); var LocalMediaAudioTrack = mixinLocalMediaTrack(AudioTrack); /** * A {@link LocalAudioTrack} is an {@link AudioTrack} representing audio that * your {@link LocalParticipant} can publish to a {@link Room}. It can be * enabled and disabled with {@link LocalAudioTrack#enable} and * {@link LocalAudioTrack#disable} or stopped completely with * {@link LocalAudioTrack#stop}. * @extends AudioTrack * @property {Track.ID} id - The {@link LocalAudioTrack}'s ID * @property {boolean} isMuted - Whether or not the audio source has stopped sending samples to the * {@link LocalAudioTrack}; This can happen when the microphone is taken over by another application, * mainly on mobile devices; When this property toggles, then <code>muted</code> and <code>unmuted</code> * events are fired appropriately * @property {boolean} isStopped - Whether or not the {@link LocalAudioTrack} is * stopped * @property {NoiseCancellation?} noiseCancellation - When a LocalAudioTrack is created * with {@link NoiseCancellationOptions}, this property provides interface * to enable or disable the noise cancellation at runtime. * @emits LocalAudioTrack#disabled * @emits LocalAudioTrack#enabled * @emits LocalAudioTrack#muted * @emits LocalAudioTrack#started * @emits LocalAudioTrack#stopped * @emits LocalAudioTrack#unmuted */ var LocalAudioTrack = /** @class */ (function (_super) { __extends(LocalAudioTrack, _super); /** * Construct a {@link LocalAudioTrack} from a MediaStreamTrack. * @param {MediaStreamTrack} mediaStreamTrack - An audio MediaStreamTrack * @param {LocalTrackOptions} [options] - {@link LocalTrack} options */ function LocalAudioTrack(mediaStreamTrack, options) { var _this = this; var noiseCancellation = (options === null || options === void 0 ? void 0 : options.noiseCancellation) || null; _this = _super.call(this, mediaStreamTrack, options) || this; var log = _this._log; var _a = mediaStreamTrack.label, defaultDeviceLabel = _a === void 0 ? '' : _a; var _b = mediaStreamTrack.getSettings(), _c = _b.deviceId, defaultDeviceId = _c === void 0 ? '' : _c, _d = _b.groupId, defaultGroupId = _d === void 0 ? '' : _d; Object.defineProperties(_this, { _currentDefaultDeviceInfo: { value: { deviceId: defaultDeviceId, groupId: defaultGroupId, label: defaultDeviceLabel }, writable: true }, _enumerateDevices: { value: typeof (options === null || options === void 0 ? void 0 : options.enumerateDevices) === 'function' ? options.enumerateDevices : navigator.mediaDevices.enumerateDevices }, _defaultDeviceCaptureMode: { value: (!isIOS() || !!noiseCancellation) && _this._isCreatedByCreateLocalTracks && typeof navigator === 'object' && typeof navigator.mediaDevices === 'object' && typeof navigator.mediaDevices.addEventListener === 'function' && (typeof (options === null || options === void 0 ? void 0 : options.enumerateDevices) === 'function' || typeof navigator.mediaDevices.enumerateDevices === 'function') ? (options === null || options === void 0 ? void 0 : options.defaultDeviceCaptureMode) || 'auto' : 'manual' }, _onDeviceChange: { value: function () { _this._enumerateDevices().then(function (deviceInfos) { // NOTE(mmalavalli): In Chrome, when the default device changes, and we restart the LocalAudioTrack with // device ID "default", it will not switch to the new default device unless all LocalAudioTracks capturing // from the old default device are stopped. So, we restart the LocalAudioTrack with the actual device ID of // the new default device instead. var defaultDeviceInfo = deviceInfos.find(function (_a) { var deviceId = _a.deviceId, kind = _a.kind; return kind === 'audioinput' && deviceId !== 'default'; }); if (defaultDeviceInfo && ['deviceId', 'groupId'].some(function (prop) { return defaultDeviceInfo[prop] !== _this._currentDefaultDeviceInfo[prop]; })) { log.info('Default device changed, restarting the LocalAudioTrack'); log.debug("Old default device: \"" + _this._currentDefaultDeviceInfo.deviceId + "\" => \"" + _this._currentDefaultDeviceInfo.label + "\""); log.debug("New default device: \"" + defaultDeviceInfo.deviceId + "\" => \"" + defaultDeviceInfo.label + "\""); _this._currentDefaultDeviceInfo = defaultDeviceInfo; _this._restartDefaultDevice().catch(function (error) { return log.warn("Failed to restart: " + error.message); }); } }, function (error) { log.warn("Failed to run enumerateDevices(): " + error.message); }); } }, _restartOnDefaultDeviceChangeCleanup: { value: null, writable: true }, noiseCancellation: { enumerable: true, value: noiseCancellation, writable: false }, }); log.debug('defaultDeviceCaptureMode:', _this._defaultDeviceCaptureMode); _this._maybeRestartOnDefaultDeviceChange(); return _this; } LocalAudioTrack.prototype.toString = function () { return "[LocalAudioTrack #" + this._instanceId + ": " + this.id + "]"; }; LocalAudioTrack.prototype.attach = function (el) { el = _super.prototype.attach.call(this, el); el.muted = true; return el; }; /** * @private */ LocalAudioTrack.prototype._end = function () { return _super.prototype._end.apply(this, arguments); }; /** * @private */ LocalAudioTrack.prototype._maybeRestartOnDefaultDeviceChange = function () { var _this = this; var _a = this, constraints = _a._constraints, defaultDeviceCaptureMode = _a._defaultDeviceCaptureMode, log = _a._log; var mediaStreamTrack = this.noiseCancellation ? this.noiseCancellation.sourceTrack : this.mediaStreamTrack; var deviceId = mediaStreamTrack.getSettings().deviceId; var isNotEqualToCapturedDeviceIdOrEqualToDefault = function (requestedDeviceId) { return requestedDeviceId !== deviceId || requestedDeviceId === 'default'; }; var isCapturingFromDefaultDevice = (function checkIfCapturingFromDefaultDevice(deviceIdConstraint) { if (deviceIdConstraint === void 0) { deviceIdConstraint = {}; } if (typeof deviceIdConstraint === 'string') { return isNotEqualToCapturedDeviceIdOrEqualToDefault(deviceIdConstraint); } else if (Array.isArray(deviceIdConstraint)) { return deviceIdConstraint.every(isNotEqualToCapturedDeviceIdOrEqualToDefault); } else if (deviceIdConstraint.exact) { return checkIfCapturingFromDefaultDevice(deviceIdConstraint.exact); } else if (deviceIdConstraint.ideal) { return checkIfCapturingFromDefaultDevice(deviceIdConstraint.ideal); } return true; }(constraints.deviceId)); if (defaultDeviceCaptureMode === 'auto' && isCapturingFromDefaultDevice) { if (!this._restartOnDefaultDeviceChangeCleanup) { log.info('LocalAudioTrack will be restarted if the default device changes'); navigator.mediaDevices.addEventListener('devicechange', this._onDeviceChange); this._restartOnDefaultDeviceChangeCleanup = function () { log.info('Cleaning up the listener to restart the LocalAudioTrack if the default device changes'); navigator.mediaDevices.removeEventListener('devicechange', _this._onDeviceChange); _this._restartOnDefaultDeviceChangeCleanup = null; }; } } else { log.info('LocalAudioTrack will NOT be restarted if the default device changes'); if (this._restartOnDefaultDeviceChangeCleanup) { this._restartOnDefaultDeviceChangeCleanup(); } } }; /** * @private */ LocalAudioTrack.prototype._reacquireTrack = function (constraints) { var _this = this; this._log.debug('_reacquireTrack: ', constraints); if (this.noiseCancellation) { return this.noiseCancellation.reacquireTrack(function () { return _super.prototype._reacquireTrack.call(_this, constraints); }); } return _super.prototype._reacquireTrack.call(this, constraints); }; /** * @private */ LocalAudioTrack.prototype._restartDefaultDevice = function () { var _this = this; var constraints = Object.assign({}, this._constraints); var restartConstraints = Object.assign({}, constraints, { deviceId: this._currentDefaultDeviceInfo.deviceId }); return this.restart(restartConstraints).then(function () { // NOTE(mmalavalli): Since we used the new default device's ID while restarting the LocalAudioTrack, // we reset the constraints to the original constraints so that the default device detection logic in // _maybeRestartOnDefaultDeviceChange() still works. _this._constraints = constraints; _this._maybeRestartOnDefaultDeviceChange(); }); }; /** * NOTE(mmalavalli): On iOS 17 Chrome, a LocalAudioTrack with Krisp Noise Cancellation * enabled that is restarted due to foregrounding the browser is silent for as-of-yet * unknown reason. We work around this by discarding the Krisp MediaStreamTrack and using * the source MediaStreamTrack. (VIDEO-13006) * @private */ LocalAudioTrack.prototype._setMediaStreamTrack = function (mediaStreamTrack) { var _this = this; var _a = this, log = _a._log, noiseCancellation = _a.noiseCancellation; var promise = _super.prototype._setMediaStreamTrack.call(this, mediaStreamTrack); if (isIOSChrome() && !!noiseCancellation) { log.debug('iOS Chrome detected, checking if the restarted Krisp audio is silent'); promise = promise.then(function () { return detectSilentAudio(_this._dummyEl); }).then(function (isSilent) { log.debug("Krisp audio is " + (isSilent ? 'silent, using source audio' : 'not silent')); return isSilent && noiseCancellation.disablePermanently().then(function () { return _super.prototype._setMediaStreamTrack.call(_this, noiseCancellation.sourceTrack); }); }); } return promise; }; /** * Disable the {@link LocalAudioTrack}. This is equivalent to muting the audio source. * @returns {this} * @fires LocalAudioTrack#disabled */ LocalAudioTrack.prototype.disable = function () { return _super.prototype.disable.apply(this, arguments); }; /** * Enable the {@link LocalAudioTrack}. This is equivalent to unmuting the audio source. * @returns {this} * @fires LocalAudioTrack#enabled */ /** * Enable or disable the {@link LocalAudioTrack}. This is equivalent to unmuting or muting * the audio source respectively. * @param {boolean} [enabled] - Specify false to disable the * {@link LocalAudioTrack} * @returns {this} * @fires LocalAudioTrack#disabled * @fires LocalAudioTrack#enabled */ LocalAudioTrack.prototype.enable = function () { return _super.prototype.enable.apply(this, arguments); }; /** * Restart the {@link LocalAudioTrack}. This stops the existing MediaStreamTrack * and creates a new MediaStreamTrack. If the {@link LocalAudioTrack} is being published * to a {@link Room}, then all the {@link RemoteParticipant}s will start receiving media * from the newly created MediaStreamTrack. You can access the new MediaStreamTrack via * the <code>mediaStreamTrack</code> property. If you want to listen to events on * the MediaStreamTrack directly, please do so in the "started" event handler. Also, * the {@link LocalAudioTrack}'s ID is no longer guaranteed to be the same as the * underlying MediaStreamTrack's ID. * @param {MediaTrackConstraints} [constraints] - The optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints" target="_blank">MediaTrackConstraints</a> * for restarting the {@link LocalAudioTrack}; If not specified, then the current MediaTrackConstraints * will be used; If <code>{}</code> (empty object) is specified, then the default MediaTrackConstraints * will be used * @returns {Promise<void>} Rejects with a TypeError if the {@link LocalAudioTrack} was not created * using an one of <code>createLocalAudioTrack</code>, <code>createLocalTracks</code> or <code>connect</code>; * Also rejects with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Exceptions" target="_blank">DOMException</a> * raised by <code>getUserMedia</code> when it fails * @fires LocalAudioTrack#stopped * @fires LocalAudioTrack#started * @example * const { connect, createLocalAudioTrack } = require('twilio-video'); * * // Create a LocalAudioTrack that captures audio from a USB microphone. * createLocalAudioTrack({ deviceId: 'usb-mic-id' }).then(function(localAudioTrack) { * return connect('token', { * name: 'my-cool-room', * tracks: [localAudioTrack] * }); * }).then(function(room) { * // Restart the LocalAudioTrack to capture audio from the default microphone. * const localAudioTrack = Array.from(room.localParticipant.audioTracks.values())[0].track; * return localAudioTrack.restart({ deviceId: 'default-mic-id' }); * }); */ LocalAudioTrack.prototype.restart = function () { return _super.prototype.restart.apply(this, arguments); }; /** * Calls stop on the underlying MediaStreamTrack. If you choose to stop a * {@link LocalAudioTrack}, you should unpublish it after stopping. * @returns {this} * @fires LocalAudioTrack#stopped */ LocalAudioTrack.prototype.stop = function () { if (this.noiseCancellation) { this.noiseCancellation.stop(); } if (this._restartOnDefaultDeviceChangeCleanup) { this._restartOnDefaultDeviceChangeCleanup(); } return _super.prototype.stop.apply(this, arguments); }; return LocalAudioTrack; }(LocalMediaAudioTrack)); /** * The {@link LocalAudioTrack} was disabled, i.e. the audio source was muted by the user. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was * disabled * @event LocalAudioTrack#disabled */ /** * The {@link LocalAudioTrack} was enabled, i.e. the audio source was unmuted by the user. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was enabled * @event LocalAudioTrack#enabled */ /** * The {@link LocalAudioTrack} was muted because the audio source stopped sending samples, most * likely due to another application taking said audio source, especially on mobile devices. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was muted * @event LocalAudioTrack#muted */ /** * The {@link LocalAudioTrack} started. This means there is enough audio data to * begin playback. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that started * @event LocalAudioTrack#started */ /** * The {@link LocalAudioTrack} stopped, either because {@link LocalAudioTrack#stop} * or {@link LocalAudioTrack#restart} was called or because the underlying * MediaStreamTrack ended. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that stopped * @event LocalAudioTrack#stopped */ /** * The {@link LocalAudioTrack} was unmuted because the audio source resumed sending samples, * most likely due to the application that took over the said audio source has released it * back to the application, especially on mobile devices. This event is also fired when * {@link LocalAudioTrack#restart} is called on a muted {@link LocalAudioTrack} with a * new audio source. * @param {LocalAudioTrack} track - The {@link LocalAudioTrack} that was unmuted * @event LocalAudioTrack#unmuted */ module.exports = LocalAudioTrack; //# sourceMappingURL=localaudiotrack.js.map