mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
feat(dictionary): add Hachidori backend support
- Add backend selection, setup gating, Anki integration, and external host support - Add launcher flags, documentation, packaging, and focused tests - Open on-demand overlay modals on the first attempt
This commit is contained in:
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// Chrome may return an empty list until its first voiceschanged event. Keep
|
||||
// this wait inside the caller's existing deadline and cancellation.
|
||||
function waitForSpeechVoices(speech, signal) {
|
||||
signal.throwIfAborted();
|
||||
return new Promise((resolve, reject) => {
|
||||
function clean() {
|
||||
speech.removeEventListener("voiceschanged", changed);
|
||||
signal.removeEventListener("abort", aborted);
|
||||
}
|
||||
function changed() { clean(); resolve(speech.getVoices()); }
|
||||
function aborted() { clean(); reject(signal.reason); }
|
||||
speech.addEventListener("voiceschanged", changed, { once: true });
|
||||
signal.addEventListener("abort", aborted, { once: true });
|
||||
// The voice service can finish loading between the initial query and the
|
||||
// listener registration; checking again avoids missing that transition.
|
||||
if (speech.getVoices().length) changed();
|
||||
});
|
||||
}
|
||||
|
||||
export async function resolveSpeech(window, source, term, signal) {
|
||||
const speech = window.speechSynthesis;
|
||||
let voices = speech.getVoices();
|
||||
if (!voices.length) voices = await waitForSpeechVoices(speech, signal);
|
||||
signal.throwIfAborted();
|
||||
if (voices.length === 0) throw new Error("No speech voices are available in this browser.");
|
||||
const text = source.type === "text-to-speech-reading" ? term.reading || term.expression : term.expression;
|
||||
const utterance = new window.SpeechSynthesisUtterance(text);
|
||||
utterance.lang = "ja-JP";
|
||||
const japanese = voices.filter(voice => /^ja(?:[-_]|$)/i.test(voice.lang));
|
||||
const voice = source.voice
|
||||
? voices.find(value => value.voiceURI === source.voice || value.name === source.voice)
|
||||
: japanese.find(value => /^Google\b/i.test(value.name))
|
||||
|| japanese.find(value => value.default) || japanese[0];
|
||||
if (source.voice && !voice) {
|
||||
throw new Error("The selected speech voice is no longer available. Choose another voice in Audio Settings.");
|
||||
}
|
||||
if (!voice) {
|
||||
throw new Error("No Japanese speech voice is available. Install a Japanese voice or add an audio provider in Audio Settings.");
|
||||
}
|
||||
utterance.voice = voice;
|
||||
return {
|
||||
speech,
|
||||
utterance,
|
||||
candidate: { name: voice.name, text, voice: source.voice, index: 0 },
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user