Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 1x 1x 5x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 4x 4x 4x 4x 5x 1x 1x 1x 1x 1x 1x 1x 5x |
/**
*
* AudioInputHandler is a microphone input handler that:
* Captures audio from the user’s microphone.
* Processes audio in chunks (Float32Array).
* Sends those chunks to a callback function for further processing.
* It also provides start/stop control and exposes the audio sample rate.
*/
export class AudioInputHandler {
/** Media stream from users microphone */
private stream: MediaStream | null = null;
/** Used for processing the audio */
private ctx: AudioContext | null = null;
/** Used to buffer audio data*/
private processor: ScriptProcessorNode | null = null;
/** Flag that checks if startListening has already been called */
public isListening: boolean = false;
/** Callback function that receives each audio chunk captured from the microphone. */
private onAudioChunk: (chunk: Float32Array) => void;
/**
* Creates a new AudioInputHandler.
*
* @param onAudioChunk - A callback function that is called whenever
* an audio chunk is captured. Receives a Float32Array
* containing the audio samples.
*/
constructor(onAudioChunk: (chunk: Float32Array) => void) {
this.onAudioChunk = onAudioChunk;
}
/**
* Returns the sample rate of the audio context.
*
* @returns The sample rate in Hz, or `undefined` if the audio context is not initialized.
*/
public getSampleRate() {
return this.ctx?.sampleRate ?? 0;
}
/**
* Starts capturing audio from the user's microphone.
*
* - Prompts the user for microphone permissions.
* - Creates an AudioContext and a ScriptProcessorNode to process audio in chunks.
* - Calls the `onAudioChunk` callback with a Float32Array for each audio buffer.
* - Handles errors such as permission denial or missing microphone hardware.
*
* @returns A Promise that resolves when listening has started.
*/
public async startListening(): Promise<void> {
//bail if mic already running
if (this.isListening) {
console.log('already listening...');
}
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error("This device does not support microphone input.")
return;
}
try {
// this line asks for user perms and starts rec
this.stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.ctx = new AudioContext();
const source = this.ctx.createMediaStreamSource(this.stream);
this.processor = this.ctx.createScriptProcessor(8192, 1, 1);
//connect audio graph nodes together
source.connect(this.processor);
this.processor.connect(this.ctx.destination); // connecting to speaker even though we are not outputting audio because apparently it might crash if not?
//onaudioprocess that auto exexcutes when buffer full, e is the event data itself
this.processor.onaudioprocess = (e) => {
const input = e.inputBuffer.getChannelData(0); // this returns a Float32Array
this.onAudioChunk(new Float32Array(input));
};
this.isListening = true;
console.log("Microphone is listening...")
} catch (err: any) {
//updating so we dont have generic message and we know why the catch is hitting
if (err.name === "NotAllowedError"){
console.error("You may have denied microphone permissions... please try again");
} else if (err.name === "NotFoundError") {
console.error("No microphone was found on this device");
} else {
console.error("Error accessing Mic: " + err);
}
}
}
/**
* Stops capturing audio from the microphone and cleans up resources.
*
* - Disconnects the ScriptProcessorNode from the audio graph.
* - Closes the AudioContext.
* - Stops all tracks of the MediaStream.
* - Updates the `isListening` flag to `false`.
*/
public stopListening(): void {
// if never started, bail immediately
if (!this.isListening) return;
this.processor?.disconnect();
this.ctx?.close();
this.stream?.getTracks().forEach((track) => track.stop());
this.isListening = false;
console.log('Stopped listening...');
}
}
|