1 | import { __awaiter } from "tslib";
|
2 | import { connect, ToneAudioNode, } from "../core/context/ToneAudioNode.js";
|
3 | import { Volume } from "../component/channel/Volume.js";
|
4 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
5 | import { assert } from "../core/util/Debug.js";
|
6 | import { readOnly } from "../core/util/Interface.js";
|
7 | import { 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 | */
|
27 | export 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 |