Add TTS volume, a browser preview, and the split-audio plan

This commit is contained in:
Fisto
2026-09-22 09:27:14 +02:00
parent d99d614c60
commit 420fdca3cf
11 changed files with 287 additions and 73 deletions
+21 -4
View File
@@ -156,7 +156,7 @@ export class AudioService {
* grows. The wrapper touches `<file>.ready` after the first word, which is the reliable
* signal; a size threshold and a timeout cover providers that do not mark readiness.
*/
private async playWhenReady(file: string): Promise<void> {
private async playWhenReady(file: string, volume?: number): Promise<void> {
for (let attempt = 0; attempt < 400; attempt++) {
try {
if (statSync(`${file}.ready`).size >= 0) break;
@@ -172,7 +172,7 @@ export class AudioService {
}
try {
await this.manager.play(file);
await this.manager.play(file, volume);
} finally {
try {
unlinkSync(`${file}.ready`);
@@ -182,6 +182,23 @@ export class AudioService {
}
}
/** The TTS output directory, for serving preview files. */
get ttsDirectory(): string {
return this.tts.directory;
}
/**
* Synthesize a line for browser playback. This never touches the call:
* no queue, no ducking, no virtual microphone, no voice-processing bypass.
*/
async preview(text: string, options: TtsOptions = {}): Promise<string> {
const trimmed = text.trim();
if (!trimmed) throw new Error("text is required");
if (trimmed.length > 1000) throw new Error("text is too long (max 1000 characters)");
return this.tts.synthesize(trimmed, { ...options, stream: false, onOutputPath: undefined });
}
stop(): void {
this.queue.length = 0;
this.manager.stopPlayback();
@@ -402,7 +419,7 @@ export class AudioService {
options.onOutputPath = path => {
streaming = (async () => {
await this.applyDuck(this.shouldDuck());
await this.playWhenReady(path);
await this.playWhenReady(path, item.options.volume);
})();
};
}
@@ -413,7 +430,7 @@ export class AudioService {
await pending;
} else {
await this.applyDuck(this.shouldDuck());
await this.manager.play(file);
await this.manager.play(file, item.options.volume);
}
} catch (error) {
console.error("[audio] speak failed:", (error as Error).message);