UNPKG

7.66 kBJavaScriptView Raw
1import { __awaiter } from "tslib";
2import { connect, ToneAudioNode, } from "../core/context/ToneAudioNode.js";
3import { Volume } from "../component/channel/Volume.js";
4import { optionsFromArguments } from "../core/util/Defaults.js";
5import { assert } from "../core/util/Debug.js";
6import { readOnly } from "../core/util/Interface.js";
7import { isDefined, isNumber } from "../core/util/TypeCheck.js";
8/**
9 * UserMedia uses MediaDevices.getUserMedia to open up and external microphone or audio input.
10 * Check [MediaDevices API Support](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
11 * to see which browsers are supported. Access to an external input
12 * is limited to secure (HTTPS) connections.
13 * @example
14 * const meter = new Tone.Meter();
15 * const mic = new Tone.UserMedia().connect(meter);
16 * mic.open().then(() => {
17 * // promise resolves when input is available
18 * console.log("mic open");
19 * // print the incoming mic levels in decibels
20 * setInterval(() => console.log(meter.getValue()), 100);
21 * }).catch(e => {
22 * // promise is rejected when the user doesn't have or allow mic access
23 * console.log("mic not open");
24 * });
25 * @category Source
26 */
27export class UserMedia extends ToneAudioNode {
28 constructor() {
29 const options = optionsFromArguments(UserMedia.getDefaults(), arguments, ["volume"]);
30 super(options);
31 this.name = "UserMedia";
32 this._volume = this.output = new Volume({
33 context: this.context,
34 volume: options.volume,
35 });
36 this.volume = this._volume.volume;
37 readOnly(this, "volume");
38 this.mute = options.mute;
39 }
40 static getDefaults() {
41 return Object.assign(ToneAudioNode.getDefaults(), {
42 mute: false,
43 volume: 0,
44 });
45 }
46 /**
47 * Open the media stream. If a string is passed in, it is assumed
48 * to be the label or id of the stream, if a number is passed in,
49 * it is the input number of the stream.
50 * @param labelOrId The label or id of the audio input media device.
51 * With no argument, the default stream is opened.
52 * @return The promise is resolved when the stream is open.
53 */
54 open(labelOrId) {
55 return __awaiter(this, void 0, void 0, function* () {
56 assert(UserMedia.supported, "UserMedia is not supported");
57 // close the previous stream
58 if (this.state === "started") {
59 this.close();
60 }
61 const devices = yield UserMedia.enumerateDevices();
62 if (isNumber(labelOrId)) {
63 this._device = devices[labelOrId];
64 }
65 else {
66 this._device = devices.find((device) => {
67 return (device.label === labelOrId || device.deviceId === labelOrId);
68 });
69 // didn't find a matching device
70 if (!this._device && devices.length > 0) {
71 this._device = devices[0];
72 }
73 assert(isDefined(this._device), `No matching device ${labelOrId}`);
74 }
75 // do getUserMedia
76 const constraints = {
77 audio: {
78 echoCancellation: false,
79 sampleRate: this.context.sampleRate,
80 noiseSuppression: false,
81 mozNoiseSuppression: false,
82 },
83 };
84 if (this._device) {
85 // @ts-ignore
86 constraints.audio.deviceId = this._device.deviceId;
87 }
88 const stream = yield navigator.mediaDevices.getUserMedia(constraints);
89 // start a new source only if the previous one is closed
90 if (!this._stream) {
91 this._stream = stream;
92 // Wrap a MediaStreamSourceNode around the live input stream.
93 const mediaStreamNode = this.context.createMediaStreamSource(stream);
94 // Connect the MediaStreamSourceNode to a gate gain node
95 connect(mediaStreamNode, this.output);
96 this._mediaStream = mediaStreamNode;
97 }
98 return this;
99 });
100 }
101 /**
102 * Close the media stream
103 */
104 close() {
105 if (this._stream && this._mediaStream) {
106 this._stream.getAudioTracks().forEach((track) => {
107 track.stop();
108 });
109 this._stream = undefined;
110 // remove the old media stream
111 this._mediaStream.disconnect();
112 this._mediaStream = undefined;
113 }
114 this._device = undefined;
115 return this;
116 }
117 /**
118 * Returns a promise which resolves with the list of audio input devices available.
119 * @return The promise that is resolved with the devices
120 * @example
121 * Tone.UserMedia.enumerateDevices().then((devices) => {
122 * // print the device labels
123 * console.log(devices.map(device => device.label));
124 * });
125 */
126 static enumerateDevices() {
127 return __awaiter(this, void 0, void 0, function* () {
128 const allDevices = yield navigator.mediaDevices.enumerateDevices();
129 return allDevices.filter((device) => {
130 return device.kind === "audioinput";
131 });
132 });
133 }
134 /**
135 * Returns the playback state of the source, "started" when the microphone is open
136 * and "stopped" when the mic is closed.
137 */
138 get state() {
139 return this._stream && this._stream.active ? "started" : "stopped";
140 }
141 /**
142 * Returns an identifier for the represented device that is
143 * persisted across sessions. It is un-guessable by other applications and
144 * unique to the origin of the calling application. It is reset when the
145 * user clears cookies (for Private Browsing, a different identifier is
146 * used that is not persisted across sessions). Returns undefined when the
147 * device is not open.
148 */
149 get deviceId() {
150 if (this._device) {
151 return this._device.deviceId;
152 }
153 else {
154 return undefined;
155 }
156 }
157 /**
158 * Returns a group identifier. Two devices have the
159 * same group identifier if they belong to the same physical device.
160 * Returns null when the device is not open.
161 */
162 get groupId() {
163 if (this._device) {
164 return this._device.groupId;
165 }
166 else {
167 return undefined;
168 }
169 }
170 /**
171 * Returns a label describing this device (for example "Built-in Microphone").
172 * Returns undefined when the device is not open or label is not available
173 * because of permissions.
174 */
175 get label() {
176 if (this._device) {
177 return this._device.label;
178 }
179 else {
180 return undefined;
181 }
182 }
183 /**
184 * Mute the output.
185 * @example
186 * const mic = new Tone.UserMedia();
187 * mic.open().then(() => {
188 * // promise resolves when input is available
189 * });
190 * // mute the output
191 * mic.mute = true;
192 */
193 get mute() {
194 return this._volume.mute;
195 }
196 set mute(mute) {
197 this._volume.mute = mute;
198 }
199 dispose() {
200 super.dispose();
201 this.close();
202 this._volume.dispose();
203 this.volume.dispose();
204 return this;
205 }
206 /**
207 * If getUserMedia is supported by the browser.
208 */
209 static get supported() {
210 return (isDefined(navigator.mediaDevices) &&
211 isDefined(navigator.mediaDevices.getUserMedia));
212 }
213}
214//# sourceMappingURL=UserMedia.js.map
\No newline at end of file