Podcast voices: natural provider TTS, and system-voice hygiene
Two reports, one root: the platform's speech engine. "Loud cracking at the end of every sentence" is the browser/OS TTS popping between its internal sentence chunks (an idle cancel makes it worse). Hygiene for that path: never cancel when nothing is pending, normalise the spoken text (control characters, ellipses, dashes), and a 140ms beat between sections so speech never runs into the engine's tail. The real fix is a second voice source that does not use the platform engine: - lib/providers carries TTS profiles (OpenAI gpt-4o-mini-tts, Groq playai-tts, any OpenAI-compatible /audio/speech behind the custom provider), male voices first. - /api/tts proxies them with the same guards and the key still in headers. - the picker gains System / Natural, with the provider's voice list. - lib/podcast-audio plays one clip per section through a hidden <audio> element, measures the clip's real duration and gives each word a slice of it proportional to its spoken length - the highlight follows the audio exactly, and pause/seek/chapters become real audio operations. Clips cache per session, the next section generates while the current plays, and a voice change disposes the element and its blobs. Speed applies live (setRate) without dropping the cache. podcast-test stubs /api/tts with a real silent WAV and pins the path: the voice list, the request body, exactly one playing element, and the highlight riding currentTime. 43 checks. tests/lib.mjs launches Chromium with the autoplay policy relaxed (a real device has the listener's gesture). Version 0.7.6, SW v98.
This commit is contained in:
parent
9882d775e7
commit
8e1cd0855d
13 changed files with 997 additions and 63 deletions
|
|
@ -60,7 +60,13 @@ export function resolveChrome() {
|
|||
export const CHROME = resolveChrome();
|
||||
|
||||
export async function launchBrowser() {
|
||||
return chromium.launch({ executablePath: CHROME, args: ["--no-sandbox"] });
|
||||
return chromium.launch({
|
||||
executablePath: CHROME,
|
||||
/* The podcast player plays generated audio through an <audio> element;
|
||||
headless Chromium would refuse it without this flag (a real device has
|
||||
the listener's own gesture, which is the policy's real gate). */
|
||||
args: ["--no-sandbox", "--autoplay-policy=no-user-gesture-required"],
|
||||
});
|
||||
}
|
||||
|
||||
/** axe-core source, injected into the page for the contrast audit. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue