Refactor STT
This commit is contained in:
@@ -4,12 +4,12 @@
|
||||
* decides what to do with the result.
|
||||
*
|
||||
* Recording lifecycle: acquire() requests microphone access (async,
|
||||
* may prompt) and creates a fresh MediaRecorder; start() begins
|
||||
* capturing (synchronous); stop() resolves the audio blob and
|
||||
* releases the stream. A fresh stream is acquired per recording and
|
||||
* torn down on every stop() so iOS/WebKit never reuses a capture
|
||||
* track that TTS playback or an audio-session interruption has
|
||||
* quietly killed.
|
||||
* may prompt) and creates the MediaRecorder; start() begins capturing
|
||||
* (synchronous); stop() resolves the audio blob. The stream and
|
||||
* recorder are reused across recordings to avoid codec warmup delay.
|
||||
* Before each acquire(), the track state is checked; if iOS/WebKit
|
||||
* has killed the track due to TTS playback or an audio-session
|
||||
* interruption, a fresh stream is obtained.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -41,19 +41,29 @@ export class STT {
|
||||
}
|
||||
|
||||
/**
|
||||
* acquire requests microphone access and creates a fresh
|
||||
* MediaRecorder. Always starts clean so iOS never reuses a dead
|
||||
* capture track. Must be called before start.
|
||||
* acquire requests microphone access and creates the MediaRecorder.
|
||||
* If an existing recorder has a live track, it is reused to avoid
|
||||
* codec warmup delay. If the track is dead (e.g., killed by iOS
|
||||
* after TTS playback), a fresh stream and recorder are created.
|
||||
* Must be called before start.
|
||||
*/
|
||||
async acquire() {
|
||||
// Reuse if the existing track is still live.
|
||||
const track = this.mediaRecorder?.stream?.getAudioTracks()[0];
|
||||
if (track?.readyState === 'live') {
|
||||
this.audioChunks = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Track is dead or doesn't exist — acquire fresh.
|
||||
this._release();
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: true,
|
||||
});
|
||||
|
||||
const [track] = stream.getAudioTracks();
|
||||
if (track?.readyState !== 'live') {
|
||||
const [newTrack] = stream.getAudioTracks();
|
||||
if (newTrack?.readyState !== 'live') {
|
||||
for (const t of stream.getTracks()) t.stop();
|
||||
throw new Error('microphone unavailable');
|
||||
}
|
||||
@@ -93,6 +103,7 @@ export class STT {
|
||||
|
||||
/**
|
||||
* stop stops recording, transcribes the audio, and returns the result.
|
||||
* The stream and recorder are kept alive for reuse.
|
||||
* @returns {Promise<{text: string, detectedLanguage: string}>}
|
||||
*/
|
||||
async stop() {
|
||||
@@ -118,10 +129,10 @@ export class STT {
|
||||
|
||||
/**
|
||||
* _stopRecording stops the MediaRecorder and resolves with the
|
||||
* assembled audio blob, releasing the stream. Returns an empty blob
|
||||
* if there is no active recording, so it is safe to call before
|
||||
* start has run (e.g. if the user released while acquire was still
|
||||
* pending).
|
||||
* assembled audio blob. Does not release the stream or recorder,
|
||||
* so they can be reused. Returns an empty blob if there is no
|
||||
* active recording, so it is safe to call before start has run
|
||||
* (e.g., if the user released while acquire was still pending).
|
||||
* @returns {Promise<Blob>}
|
||||
* @private
|
||||
*/
|
||||
@@ -129,7 +140,6 @@ export class STT {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mr = this.mediaRecorder;
|
||||
if (!mr || mr.state === 'inactive') {
|
||||
this._release();
|
||||
resolve(new Blob([]));
|
||||
return;
|
||||
}
|
||||
@@ -142,7 +152,7 @@ export class STT {
|
||||
if (type?.includes('mp4') || type?.includes('m4a')) {
|
||||
this._ext = 'm4a';
|
||||
}
|
||||
this._release();
|
||||
this.audioChunks = [];
|
||||
resolve(blob);
|
||||
},
|
||||
{ once: true },
|
||||
@@ -151,7 +161,7 @@ export class STT {
|
||||
mr.addEventListener(
|
||||
'error',
|
||||
(event) => {
|
||||
this._release();
|
||||
this.audioChunks = [];
|
||||
reject(event.error);
|
||||
},
|
||||
{ once: true },
|
||||
|
||||
Reference in New Issue
Block a user