Fix the speak status tracker and null option handling
This commit is contained in:
@@ -40,6 +40,13 @@ mobile draft at 390px.
|
||||
- [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB
|
||||
cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`:
|
||||
`previewed: 2`; thumbs served over `/api/media/<scrape>/<item>/thumb` (200).
|
||||
- [x] C70 Speak panel honesty. Two bugs behind "speak stays queued": the panel never updated
|
||||
`#tts-status` after queuing (now it polls the daemon and shows `speaking…`, `queued (N)`,
|
||||
`done`, then `idle`), and `/api/audio/speak` turned the panel's `null` option fields into 0 via
|
||||
`Number(null)`, so the wrapper clamped `crunchBits` to 1 bit and set the echo level to 0. Null,
|
||||
undefined, and "" now all mean "unset" for every optional numeric speak field. Verified: the
|
||||
UI payload plays a healthy stream (-8.7 dB peak, 7 word gaps), and HL90 at 8 bits differs
|
||||
clearly from the old accidental 1-bit crush.
|
||||
- [x] C69 Streaming silence fix. Word mode with streaming on played nothing but the hum: the
|
||||
daemon started ffplay as soon as the streamed file passed 44 bytes (the bare WAV header), so
|
||||
the player hit EOF and exited before the first word landed. The wrapper now touches
|
||||
|
||||
@@ -1715,6 +1715,45 @@ function fillDeviceSelect(select, devices, defaultLabel) {
|
||||
if ([...select.options].some(option => option.value === current)) select.value = current;
|
||||
}
|
||||
|
||||
// The daemon plays asynchronously; keep the panel honest about queued vs speaking vs done.
|
||||
let speechTracker = 0;
|
||||
|
||||
async function trackSpeech() {
|
||||
const token = ++speechTracker;
|
||||
|
||||
for (let attempt = 0; attempt < 1200; attempt++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
if (token !== speechTracker) return;
|
||||
|
||||
try {
|
||||
const status = await VK.api.get("/api/audio");
|
||||
if (token !== speechTracker) return;
|
||||
|
||||
const label = $("#tts-status");
|
||||
if (status?.playing === true) {
|
||||
label.textContent = "speaking\u2026";
|
||||
continue;
|
||||
}
|
||||
|
||||
const queue = Number(status?.queue ?? 0);
|
||||
if (queue > 0) {
|
||||
label.textContent = `queued (${queue})`;
|
||||
continue;
|
||||
}
|
||||
|
||||
label.textContent = "done";
|
||||
setTimeout(() => {
|
||||
if (token === speechTracker && $("#tts-status").textContent === "done") {
|
||||
$("#tts-status").textContent = "idle";
|
||||
}
|
||||
}, 4000);
|
||||
return;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAudio() {
|
||||
try {
|
||||
const status = await VK.api.get("/api/audio");
|
||||
@@ -2344,6 +2383,7 @@ async function speakText() {
|
||||
$("#tts-status").textContent = "queued";
|
||||
toast("Queued for speech", "ok");
|
||||
void refreshAudio();
|
||||
void trackSpeech();
|
||||
} catch (error) {
|
||||
toast(error.message, "error");
|
||||
}
|
||||
|
||||
+17
-17
@@ -754,17 +754,21 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
? requested
|
||||
: prefs.tts.provider;
|
||||
const voice = req.body?.voice ? String(req.body.voice) : prefs.tts.voice ?? undefined;
|
||||
const speed = req.body?.speed !== undefined ? Number(req.body.speed) : prefs.tts.speed ?? undefined;
|
||||
const pitch = req.body?.pitch !== undefined ? Number(req.body.pitch) : prefs.tts.pitch ?? undefined;
|
||||
const expression =
|
||||
req.body?.expression !== undefined ? Number(req.body.expression) : prefs.tts.expression ?? undefined;
|
||||
// Number(null) is 0: null, undefined, and "" all mean "unset".
|
||||
const optional = (value: unknown): number | undefined => {
|
||||
if (value === undefined || value === null || value === "") return undefined;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : undefined;
|
||||
};
|
||||
|
||||
const speed = optional(req.body?.speed) ?? prefs.tts.speed ?? undefined;
|
||||
const pitch = optional(req.body?.pitch) ?? prefs.tts.pitch ?? undefined;
|
||||
const expression = optional(req.body?.expression) ?? prefs.tts.expression ?? undefined;
|
||||
const profile = req.body?.profile && typeof req.body.profile === "object" ? req.body.profile : prefs.tts.profile;
|
||||
const highpass =
|
||||
req.body?.highpass !== undefined ? Number(req.body.highpass) : prefs.tts.highpass ?? undefined;
|
||||
const echo = req.body?.echo !== undefined ? Number(req.body.echo) : prefs.tts.echo ?? undefined;
|
||||
const highpass = optional(req.body?.highpass) ?? prefs.tts.highpass ?? undefined;
|
||||
const echo = optional(req.body?.echo) ?? prefs.tts.echo ?? undefined;
|
||||
const words = req.body?.words !== undefined ? req.body.words === true : prefs.tts.words;
|
||||
const wordGapMs =
|
||||
req.body?.wordGapMs !== undefined ? Number(req.body.wordGapMs) : prefs.tts.wordGapMs ?? undefined;
|
||||
const wordGapMs = optional(req.body?.wordGapMs) ?? prefs.tts.wordGapMs ?? undefined;
|
||||
// The Half-Life VOX voice carries its prosody with it.
|
||||
const vox =
|
||||
(req.body?.vox !== undefined ? req.body.vox === true : prefs.tts.vox) || voice === "vox";
|
||||
@@ -775,20 +779,16 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
: req.body?.preset === null || req.body?.preset === ""
|
||||
? null
|
||||
: prefs.tts.preset;
|
||||
const echoDelayMs =
|
||||
req.body?.echoDelayMs !== undefined ? Number(req.body.echoDelayMs) : prefs.tts.echoDelayMs ?? undefined;
|
||||
const echoLevel =
|
||||
req.body?.echoLevel !== undefined ? Number(req.body.echoLevel) : prefs.tts.echoLevel ?? undefined;
|
||||
const echoDelayMs = optional(req.body?.echoDelayMs) ?? prefs.tts.echoDelayMs ?? undefined;
|
||||
const echoLevel = optional(req.body?.echoLevel) ?? prefs.tts.echoLevel ?? undefined;
|
||||
const crunch =
|
||||
["hl90", "hl90hq", "telephone", "adpcm", "crush"].includes(String(req.body?.crunch ?? ""))
|
||||
? (String(req.body.crunch) as "hl90" | "hl90hq" | "telephone" | "adpcm" | "crush")
|
||||
: req.body?.crunch === null || req.body?.crunch === "" || req.body?.crunch === "off"
|
||||
? undefined
|
||||
: prefs.tts.crunch ?? undefined;
|
||||
const crunchBits =
|
||||
req.body?.crunchBits !== undefined ? Number(req.body.crunchBits) : prefs.tts.crunchBits ?? undefined;
|
||||
const crunchRate =
|
||||
req.body?.crunchRate !== undefined ? Number(req.body.crunchRate) : prefs.tts.crunchRate ?? undefined;
|
||||
const crunchBits = optional(req.body?.crunchBits) ?? prefs.tts.crunchBits ?? undefined;
|
||||
const crunchRate = optional(req.body?.crunchRate) ?? prefs.tts.crunchRate ?? undefined;
|
||||
const streamWords =
|
||||
req.body?.streamWords !== undefined ? req.body.streamWords !== false : prefs.tts.streamWords;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user