|
@@ -1,165 +1,188 @@
|
|
|
import { mlog } from "./mjapi";
|
|
|
|
|
|
-export interface recType{
|
|
|
- timeOut:number
|
|
|
- asrLanguage?:string
|
|
|
- listener?: (result: string) => void
|
|
|
- onEnd?: () => void
|
|
|
- onStart?: () => void
|
|
|
+export interface recType {
|
|
|
+ timeOut: number;
|
|
|
+ asrLanguage?: string;
|
|
|
+ listener?: (result: string) => void;
|
|
|
+ onEnd?: () => void;
|
|
|
+ onStart?: () => void;
|
|
|
}
|
|
|
export class Recognition {
|
|
|
- private recognition: any;
|
|
|
- private listener?: (result: string) => void;
|
|
|
- private isStop = false;
|
|
|
- private recOpt: recType = { timeOut: 2000 };
|
|
|
- private handleTime: any;
|
|
|
- private hTime: Date | undefined;
|
|
|
- private asrLanguage = "cmn-Hans-CN";
|
|
|
- private onEnd?: () => void;
|
|
|
- private onStart?: () => void;
|
|
|
-
|
|
|
- public setListener(fn: (result: string) => void) {
|
|
|
- this.listener = fn;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public setOnEnd(fn: () => void) {
|
|
|
- this.onEnd = fn;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public setOpt(opt: recType) {
|
|
|
- this.recOpt = opt;
|
|
|
- if (opt.listener) this.setListener(opt.listener);
|
|
|
- if (opt.onEnd) this.setOnEnd(opt.onEnd);
|
|
|
- if (opt.asrLanguage) this.setLang(opt.asrLanguage);
|
|
|
- if (opt.onStart) this.onStart = opt.onStart;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public setLang(lang: string) {
|
|
|
- this.asrLanguage = lang;
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public start() {
|
|
|
- this.isStop = false;
|
|
|
- if (typeof window === "undefined" || (!window.SpeechRecognition && !window.webkitSpeechRecognition)) {
|
|
|
- console.warn("当前浏览器不支持 SpeechRecognition,请使用 Chrome 或 Edge");
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!this.recognition) {
|
|
|
- const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
|
|
- this.recognition = recognition;
|
|
|
- }
|
|
|
- const recognition = this.recognition;
|
|
|
-
|
|
|
- recognition.interimResults = true;
|
|
|
- recognition.lang = this.asrLanguage;
|
|
|
- recognition.continuous = true;
|
|
|
-
|
|
|
- this.hTime = new Date();
|
|
|
- this.handleTime = setInterval(() => this.check(this), this.recOpt.timeOut);
|
|
|
-
|
|
|
- recognition.addEventListener("result", (event: any) => {
|
|
|
- let transcript = "";
|
|
|
- for (let index = 0; index < event.results.length; index++) {
|
|
|
- const item = event.results[index];
|
|
|
- if (transcript && this.asrLanguage.includes("Han")) transcript += ",";
|
|
|
- transcript += (item as unknown as SpeechRecognitionAlternative[])[0]?.transcript;
|
|
|
- }
|
|
|
- if (!transcript) return;
|
|
|
- this.hTime = new Date();
|
|
|
- this.listener?.(transcript);
|
|
|
- });
|
|
|
-
|
|
|
- recognition.addEventListener("end", () => {
|
|
|
- if (this.isStop) {
|
|
|
- this.onEnd?.();
|
|
|
- clearInterval(this.handleTime);
|
|
|
- return;
|
|
|
- }
|
|
|
- setTimeout(() => recognition.start(), 1000); // 避免 Mac 触发安全限制
|
|
|
- });
|
|
|
-
|
|
|
- recognition.start();
|
|
|
- this.onStart?.();
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- public stop() {
|
|
|
- this.isStop = true;
|
|
|
- this.recognition?.stop();
|
|
|
- return this;
|
|
|
- }
|
|
|
-
|
|
|
- private check(that: Recognition) {
|
|
|
- if (!that.hTime) return;
|
|
|
- const dt = new Date().getTime() - that.hTime.getTime();
|
|
|
- if (dt > that.recOpt.timeOut) that.stop();
|
|
|
- }
|
|
|
+ private recognition: any;
|
|
|
+ private listener?: (result: string) => void;
|
|
|
+ private isStop = false;
|
|
|
+ private recOpt: recType = { timeOut: 2000 };
|
|
|
+ private handleTime: any;
|
|
|
+ private hTime: Date | undefined;
|
|
|
+ private asrLanguage = "cmn-Hans-CN";
|
|
|
+ private onEnd?: () => void;
|
|
|
+ private onStart?: () => void;
|
|
|
+
|
|
|
+ public setListener(fn: (result: string) => void) {
|
|
|
+ this.listener = fn;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public setOnEnd(fn: () => void) {
|
|
|
+ this.onEnd = fn;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public setOpt(opt: recType) {
|
|
|
+ this.recOpt = opt;
|
|
|
+ if (opt.listener) this.setListener(opt.listener);
|
|
|
+ if (opt.onEnd) this.setOnEnd(opt.onEnd);
|
|
|
+ if (opt.asrLanguage) this.setLang(opt.asrLanguage);
|
|
|
+ if (opt.onStart) this.onStart = opt.onStart;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public setLang(lang: string) {
|
|
|
+ this.asrLanguage = lang;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public start() {
|
|
|
+ this.isStop = false;
|
|
|
+ if (
|
|
|
+ typeof window === "undefined" ||
|
|
|
+ (!window.SpeechRecognition && !window.webkitSpeechRecognition)
|
|
|
+ ) {
|
|
|
+ console.warn("当前浏览器不支持 SpeechRecognition,请使用 Chrome 或 Edge");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.recognition) {
|
|
|
+ const recognition = new (window.SpeechRecognition ||
|
|
|
+ window.webkitSpeechRecognition)();
|
|
|
+ this.recognition = recognition;
|
|
|
+ }
|
|
|
+ const recognition = this.recognition;
|
|
|
+
|
|
|
+ recognition.interimResults = true;
|
|
|
+ recognition.lang = this.asrLanguage;
|
|
|
+ recognition.continuous = true;
|
|
|
+
|
|
|
+ this.hTime = new Date();
|
|
|
+ this.handleTime = setInterval(() => this.check(this), this.recOpt.timeOut);
|
|
|
+
|
|
|
+ recognition.addEventListener("result", (event: any) => {
|
|
|
+ let transcript = "";
|
|
|
+ for (let index = 0; index < event.results.length; index++) {
|
|
|
+ const item = event.results[index];
|
|
|
+ if (transcript && this.asrLanguage.includes("Han")) transcript += ",";
|
|
|
+ transcript += (item as unknown as SpeechRecognitionAlternative[])[0]
|
|
|
+ ?.transcript;
|
|
|
+ }
|
|
|
+ if (!transcript) return;
|
|
|
+ this.hTime = new Date();
|
|
|
+ this.listener?.(transcript);
|
|
|
+ });
|
|
|
+
|
|
|
+ recognition.addEventListener("end", () => {
|
|
|
+ if (this.isStop) {
|
|
|
+ clearInterval(this.handleTime);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ setTimeout(() => recognition.start(), 1000);
|
|
|
+ });
|
|
|
+
|
|
|
+ recognition.start();
|
|
|
+ this.onStart?.();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ private check(that: Recognition) {
|
|
|
+ if (!that.hTime) return;
|
|
|
+ const dt = new Date().getTime() - that.hTime.getTime();
|
|
|
+ if (dt > that.recOpt.timeOut) {
|
|
|
+ that.stop(); // 只在 stop 中触发 onEnd
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public stop() {
|
|
|
+ if (this.isStop) return this; // 如果已经停止,直接返回
|
|
|
+ this.isStop = true;
|
|
|
+ this.recognition?.stop();
|
|
|
+ clearInterval(this.handleTime);
|
|
|
+ this.onEnd?.(); // 只在这里触发一次 onEnd
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ // public stop() {
|
|
|
+ // this.isStop = true;
|
|
|
+ // this.recognition?.stop();
|
|
|
+ // return this;
|
|
|
+ // }
|
|
|
+
|
|
|
+ // private check(that: Recognition) {
|
|
|
+ // if (!that.hTime) return;
|
|
|
+ // const dt = new Date().getTime() - that.hTime.getTime();
|
|
|
+ // if (dt > that.recOpt.timeOut) that.stop();
|
|
|
+ // }
|
|
|
}
|
|
|
|
|
|
-
|
|
|
export const supportLanguages: Record<string, string> = {
|
|
|
- 'cmn-Hans-CN': '普通话 (中国大陆)',
|
|
|
- 'cmn-Hans-HK': '普通话 (香港)',
|
|
|
- 'yue-Hant-HK': '粵語 (香港)',
|
|
|
- 'en-US': 'English(United States)',
|
|
|
- 'en-GB': 'English(United Kingdom)',
|
|
|
- 'en-IN': 'English(India)',
|
|
|
- 'es-ES': 'Español',
|
|
|
- 'fr-FR': 'Français',
|
|
|
- 'de-DE': 'Deutsch',
|
|
|
- 'it-IT': 'Italiano',
|
|
|
- 'ja-JP': '日本語',
|
|
|
- 'ko-KR': '한국어',
|
|
|
- 'ar-SA': 'العربية',
|
|
|
- 'pt-BR': 'Português',
|
|
|
- 'ru-RU': 'Русский',
|
|
|
- 'nl-NL': 'Nederlands',
|
|
|
- 'tr-TR': 'Türkçe',
|
|
|
- 'sv-SE': 'Svenska',
|
|
|
- 'hi-IN': 'हिन्दी',
|
|
|
- 'el-GR': 'Ελληνικά',
|
|
|
- 'he-IL': 'עברית',
|
|
|
- 'id-ID': 'Bahasa Indonesia',
|
|
|
- 'pl-PL': 'Polski',
|
|
|
- 'th-TH': 'ไทย',
|
|
|
- 'cs-CZ': 'Čeština',
|
|
|
- 'hu-HU': 'Magyar',
|
|
|
- 'da-DK': 'Dansk',
|
|
|
- 'fi-FI': 'Suomi',
|
|
|
- 'no-NO': 'Norsk',
|
|
|
- 'sk-SK': 'Slovenčina',
|
|
|
- 'uk-UA': 'Українська',
|
|
|
- 'vi-VN': 'Tiếng Việt',
|
|
|
+ "cmn-Hans-CN": "普通话 (中国大陆)",
|
|
|
+ "cmn-Hans-HK": "普通话 (香港)",
|
|
|
+ "yue-Hant-HK": "粵語 (香港)",
|
|
|
+ "en-US": "English(United States)",
|
|
|
+ "en-GB": "English(United Kingdom)",
|
|
|
+ "en-IN": "English(India)",
|
|
|
+ "es-ES": "Español",
|
|
|
+ "fr-FR": "Français",
|
|
|
+ "de-DE": "Deutsch",
|
|
|
+ "it-IT": "Italiano",
|
|
|
+ "ja-JP": "日本語",
|
|
|
+ "ko-KR": "한국어",
|
|
|
+ "ar-SA": "العربية",
|
|
|
+ "pt-BR": "Português",
|
|
|
+ "ru-RU": "Русский",
|
|
|
+ "nl-NL": "Nederlands",
|
|
|
+ "tr-TR": "Türkçe",
|
|
|
+ "sv-SE": "Svenska",
|
|
|
+ "hi-IN": "हिन्दी",
|
|
|
+ "el-GR": "Ελληνικά",
|
|
|
+ "he-IL": "עברית",
|
|
|
+ "id-ID": "Bahasa Indonesia",
|
|
|
+ "pl-PL": "Polski",
|
|
|
+ "th-TH": "ไทย",
|
|
|
+ "cs-CZ": "Čeština",
|
|
|
+ "hu-HU": "Magyar",
|
|
|
+ "da-DK": "Dansk",
|
|
|
+ "fi-FI": "Suomi",
|
|
|
+ "no-NO": "Norsk",
|
|
|
+ "sk-SK": "Slovenčina",
|
|
|
+ "uk-UA": "Українська",
|
|
|
+ "vi-VN": "Tiếng Việt",
|
|
|
};
|
|
|
|
|
|
function sleep(time: number) {
|
|
|
- return new Promise((resolve) => setTimeout(resolve, time));
|
|
|
+ return new Promise((resolve) => setTimeout(resolve, time));
|
|
|
}
|
|
|
|
|
|
//浏览器文字播放
|
|
|
-export async function speakText(content: string, callback: (playing: boolean) => void) {
|
|
|
- if (!window.speechSynthesis) return;
|
|
|
- if (speechSynthesis.speaking) {
|
|
|
- speechSynthesis.cancel();
|
|
|
- callback(false);
|
|
|
- }
|
|
|
-
|
|
|
- await sleep(300);
|
|
|
-
|
|
|
- const msg = new SpeechSynthesisUtterance(content);
|
|
|
- msg.lang = 'zh';
|
|
|
- msg.rate = 1;
|
|
|
- msg.addEventListener('end', () => {
|
|
|
- callback(false);
|
|
|
- });
|
|
|
- msg.addEventListener('error', () => {
|
|
|
- callback(false);
|
|
|
- });
|
|
|
- callback(true);
|
|
|
- speechSynthesis.speak(msg);
|
|
|
+export async function speakText(
|
|
|
+ content: string,
|
|
|
+ callback: (playing: boolean) => void
|
|
|
+) {
|
|
|
+ if (!window.speechSynthesis) return;
|
|
|
+ if (speechSynthesis.speaking) {
|
|
|
+ speechSynthesis.cancel();
|
|
|
+ callback(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ await sleep(300);
|
|
|
+
|
|
|
+ const msg = new SpeechSynthesisUtterance(content);
|
|
|
+ msg.lang = "zh";
|
|
|
+ msg.rate = 1;
|
|
|
+ msg.addEventListener("end", () => {
|
|
|
+ callback(false);
|
|
|
+ });
|
|
|
+ msg.addEventListener("error", () => {
|
|
|
+ callback(false);
|
|
|
+ });
|
|
|
+ callback(true);
|
|
|
+ speechSynthesis.speak(msg);
|
|
|
}
|