feat(voice): live dictation with browser pause restart and server audio chunking
This commit is contained in:
1 parent
446f8e4649
commit
6795edbf4b
5 files changed
+415
-77
No files matched your search
+44
-13
@@ -86,19 +86,7 @@ export class VoiceRecorder {
|
||||
this.stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true },
|
||||
});
|
||||
const type = recordingType();
|
||||
this.recorder = new MediaRecorder(this.stream, type ? { mimeType: type } : undefined);
|
||||
this.chunks = [];
|
||||
this.recorder.ondataavailable = (e) => {
|
||||
if (e.data.size > 0) this.chunks.push(e.data);
|
||||
};
|
||||
this.recorder.onstop = () => {
|
||||
const blob = this.chunks.length ? new Blob(this.chunks, { type: this.recorder?.mimeType || type || 'audio/webm' }) : null;
|
||||
this.release();
|
||||
this.done?.(blob);
|
||||
this.done = null;
|
||||
};
|
||||
this.recorder.start(1000);
|
||||
this.startSegmentRecorder();
|
||||
try {
|
||||
const Ctx = (window as any).AudioContext || (window as any).webkitAudioContext;
|
||||
this.audioCtx = new Ctx();
|
||||
@@ -113,6 +101,25 @@ export class VoiceRecorder {
|
||||
}
|
||||
}
|
||||
|
||||
private startSegmentRecorder(): void {
|
||||
if (!this.stream) return;
|
||||
const type = recordingType();
|
||||
const rec = new MediaRecorder(this.stream, type ? { mimeType: type } : undefined);
|
||||
const chunks: Blob[] = [];
|
||||
rec.ondataavailable = (e) => {
|
||||
if (e.data.size > 0) chunks.push(e.data);
|
||||
};
|
||||
rec.onstop = () => {
|
||||
const blob = chunks.length ? new Blob(chunks, { type: rec.mimeType || type || 'audio/webm' }) : null;
|
||||
this.release();
|
||||
this.done?.(blob);
|
||||
this.done = null;
|
||||
};
|
||||
this.recorder = rec;
|
||||
this.chunks = chunks;
|
||||
rec.start(1000);
|
||||
}
|
||||
|
||||
/** Input level 0..1 (roughly loudness), or null when unknown. */
|
||||
level(): number | null {
|
||||
if (!this.analyser || !this.samples) return null;
|
||||
@@ -137,6 +144,30 @@ export class VoiceRecorder {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the current MediaRecorder segment and immediately starts a new one on the same MediaStream.
|
||||
* Resolves with the finished segment's audio Blob while recording continues seamlessly.
|
||||
*/
|
||||
cutSegment(): Promise<Blob | null> {
|
||||
if (!this.stream || !this.recorder || this.recorder.state === 'inactive') {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const oldRecorder = this.recorder;
|
||||
const oldChunks = this.chunks;
|
||||
|
||||
this.startSegmentRecorder();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
oldRecorder.onstop = () => {
|
||||
const blob = oldChunks.length
|
||||
? new Blob(oldChunks, { type: oldRecorder.mimeType || 'audio/webm' })
|
||||
: null;
|
||||
resolve(blob);
|
||||
};
|
||||
oldRecorder.stop();
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.done = null;
|
||||
if (this.recorder && this.recorder.state !== 'inactive') {
|
||||
|
||||
Reference in new issue
Block a user