|
class tts2 { |
|
constructor() { |
|
console.log("tts constructor"); |
|
window.speechSynthesis.cancel(); //強制中斷之前的語音 |
|
this.synth = window.speechSynthesis; |
|
this.v_index = 0; |
|
|
|
this.last_tts = ""; |
|
|
|
// |
|
|
|
if (localStorage.getItem("ls_rate") === null) { |
|
this.u_rate = 1.2; // 語速 0.1~10 |
|
} else { |
|
this.u_rate = Number(localStorage.getItem("ls_rate")); |
|
} |
|
|
|
if (localStorage.getItem("ls_volume") === null) { |
|
this.u_volume = 0.5; //音量 0~1 |
|
} else { |
|
this.u_volume = Number(localStorage.getItem("ls_volume")); |
|
} |
|
|
|
if (localStorage.getItem("ls_pitch") === null) { |
|
this.u_pitch = 1; //語調 0.1~2 |
|
} else { |
|
this.u_pitch = Number(localStorage.getItem("ls_pitch")); |
|
} |
|
} |
|
|
|
speak2(textToSpeak) { |
|
if (textToSpeak !== null) { |
|
if (textToSpeak.length > 0) { |
|
let filter_text = this._textFilter(textToSpeak); |
|
|
|
console.log('[將要唸的語音]', filter_text); |
|
|
|
if(filter_text.length > 0){ |
|
|
|
try{ |
|
//console.log(document.getElementById("ttsCheck").checked == true ? "[語音開啟]" : "[語音關閉]"); |
|
|
|
let u = new SpeechSynthesisUtterance(); |
|
|
|
u.rate = this.u_rate; |
|
u.volume = this.u_volume; |
|
u.pitch = this.u_pitch; |
|
|
|
u.text = filter_text; |
|
|
|
u.onend = (event) => { |
|
//console.log(event.utterance.text); |
|
this.last_tts = event.utterance.text; |
|
console.log("tts.onend", event.utterance.text); |
|
}; |
|
|
|
u.onerror = (event) => { |
|
//console.log(event); |
|
console.log("tts.onerror", event); |
|
this.cancel2(); |
|
}; |
|
|
|
// |
|
let voices = this.synth.getVoices(); |
|
for (let index = 0; index < voices.length; index++) { |
|
/* |
|
"Google US English" |
|
"Google 日本語" |
|
"Google 普通话(中国大陆)" |
|
"Google 粤語(香港)" |
|
"Google 國語(臺灣)" |
|
*/ |
|
|
|
//console.log(voices[index].name); |
|
|
|
if (voices[index].name == "Microsoft HsiaoChen Online (Natural) - Chinese (Taiwan)") { //HsiaoChen (Neural) - 曉臻 (MS Edge專用) |
|
u.voice = voices[index]; |
|
break; |
|
} else if (voices[index].name == "Google 國語(臺灣)") { //Chrome專用 |
|
u.voice = voices[index]; |
|
break; |
|
} else { |
|
//u.lang = 'zh-TW'; //這邊可能會有語音又被切回系統語音的問題 |
|
} |
|
|
|
//當最後一個都還沒找到時才設u.lang |
|
if(index+1 === voices.length){ |
|
u.lang = 'zh-TW'; |
|
} |
|
} |
|
|
|
//console.log("test"); |
|
|
|
u.onstart = (event) => { |
|
//console.log(event.utterance); |
|
//console.log("tts.onstart", filter_text); |
|
|
|
console.log("tts.onstart", event.utterance.text); |
|
if (event.utterance.text === this.last_tts) { |
|
window.speechSynthesis.cancel(); |
|
console.log("tts.cancel", event.utterance.text); |
|
} |
|
}; |
|
|
|
this.synth.speak(u); |
|
}catch (e){ |
|
console.log(e); |
|
} |
|
|
|
} |
|
} |
|
} |
|
|
|
//return this; |
|
} |
|
|
|
cancel2() { |
|
console.log("tts cancel"); |
|
window.speechSynthesis.cancel(); |
|
} |
|
|
|
volume(volume_val) { |
|
let volume = Number(volume_val); |
|
if (volume >= 0 && volume <= 1) { |
|
this.u_volume = volume; |
|
localStorage.setItem("ls_volume", volume); |
|
console.log(`音量調整為: ${this.u_volume}`); |
|
} else { |
|
console.log(`超出範圍`); |
|
} |
|
} |
|
rate(rate_val) { |
|
let rate = Number(rate_val); |
|
if (rate >= 0.5 && rate <= 2) { |
|
this.u_rate = rate; |
|
localStorage.setItem("ls_rate", rate); |
|
console.log(`語速調整為: ${this.u_rate}`); |
|
} else { |
|
console.log(`超出範圍`); |
|
} |
|
} |
|
pitch(pitch_val) { |
|
let pitch = Number(pitch_val); |
|
if (pitch >= 0.1 && pitch <= 2) { |
|
this.u_pitch = pitch; |
|
localStorage.setItem("ls_pitch", pitch); |
|
console.log(`語調調整為: ${this.u_pitch}`); |
|
} else { |
|
console.log(`超出範圍`); |
|
} |
|
} |
|
reset() { |
|
//localStorage.clear(); |
|
localStorage.removeItem("ls_volume"); |
|
localStorage.removeItem("ls_rate"); |
|
localStorage.removeItem("ls_pitch"); |
|
|
|
this.u_rate = 1.2; // 語速 0.1~10 |
|
this.u_volume = 0.5; //音量 0~1 |
|
this.u_pitch = 1; //語調 0.1~2 |
|
} |
|
|
|
_textFilter(msg) { |
|
msg = msg.trim(); //去除前後空白 |
|
|
|
//網址不唸 |
|
//msg = msg.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, "網址"); |
|
msg = msg.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, ""); |
|
|
|
//全形轉半形 |
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize |
|
msg = msg.normalize('NFKC'); |
|
|
|
//過濾掉中文,英文,數字,半形空白以外的所有字元 |
|
msg = msg.replace(/[^0-9a-z\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF ]/ig, ""); // *不要用\s取代空白,因為\s包含全形空白 |
|
|
|
msg = msg.trim(); //去除前後空白 |
|
|
|
msg = msg.replace(/^(1){4,}$/g, "一一一"); |
|
msg = msg.replace(/^(2){4,}$/g, "二二二"); |
|
msg = msg.replace(/^(3){4,}$/g, "三三三"); |
|
msg = msg.replace(/^(4){4,}$/g, "四四四"); |
|
msg = msg.replace(/^(5){4,}$/g, "五五五"); |
|
msg = msg.replace(/^(6){4,}$/g, "六六六"); |
|
msg = msg.replace(/^(7){4,}$/g, "七七七"); |
|
msg = msg.replace(/^(8){4,}$/g, "八八八"); |
|
msg = msg.replace(/^(9){4,}$/g, "九九九"); |
|
|
|
msg = msg.replace(/^(w){4,}$/gi, "哇拉"); |
|
|
|
msg = msg.replace(/^484$/gi, "四八四"); |
|
msg = msg.replace(/^87$/g, "八七"); |
|
msg = msg.replace(/^94$/g, "九四"); |
|
msg = msg.replace(/^9487$/g, "九四八七"); |
|
|
|
return msg; |
|
} |
|
|
|
//舊的過濾方法 |
|
_textFilter_old(msg) { |
|
//網址不唸 |
|
//msg = msg.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, "網址"); |
|
msg = msg.replace(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g, ""); |
|
|
|
msg = msg.replace(/^(1){4,}$/g, "一一一"); |
|
msg = msg.replace(/^(2){4,}$/g, "二二二"); |
|
msg = msg.replace(/^(3){4,}$/g, "三三三"); |
|
msg = msg.replace(/^(4){4,}$/g, "四四四"); |
|
msg = msg.replace(/^(5){4,}$/g, "五五五"); |
|
msg = msg.replace(/^(6){4,}$/g, "六六六"); |
|
msg = msg.replace(/^(7){4,}$/g, "七七七"); |
|
msg = msg.replace(/^(8){4,}$/g, "八八八"); |
|
msg = msg.replace(/^(9){4,}$/g, "九九九"); |
|
|
|
msg = msg.replace(/^(w){4,}$/gi, "哇拉"); |
|
msg = msg.replace(/^(~){3,}$/g, "~~~"); |
|
msg = msg.replace(/^(\.){3,}$/g, "..."); |
|
|
|
msg = msg.replace(/^484$/gi, "四八四"); |
|
msg = msg.replace(/^87$/g, "八七"); |
|
msg = msg.replace(/^94$/g, "九四"); |
|
msg = msg.replace(/^9487$/g, "九四八七"); |
|
|
|
//過濾掉全形符號(防edge bug) |
|
msg = msg.replace(/[\uFF01-\uFF5E]/g, ""); |
|
|
|
//過濾emoji(最多3個,超過就刪除) |
|
msg = msg.replace(/(\ud83d[\ude00-\ude4f]){4,}/g, ""); |
|
|
|
//過濾標點符號 (Punctuation & Symbols) |
|
msg = msg.replace(/[\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E]/g, ""); |
|
|
|
return msg; |
|
} |
|
} |
|
|
|
// |
|
|
|
const tts = new tts2(); |
|
/* |
|
tts.speak2("大家看到我,就知道我是誰了,我就是歐付寶終結者RRRRRRRRRRRRRRRRRRRRR"); |
|
*/ |
更正一個以前寫錯的地方
第4行和第5行應該是每一句新語音都要重新new一次
之前寫法耍蠢把第4行和第5行拿來重複使用
所以
u.text
的語音內容會一直被洗掉如果有語音一直讀錯句的話要注意是不是這個問題