PageSourceSearch

https://www.samaterials.com/js/speechUtils.js

js samaterials.com collected 2026-10-02 03:31:32 UTC 6,226 bytes, 173 lines download raw bytes

1let voices = [];
2let utterance = null;
3const speechNoticeText = 'Your browser does not support the SpeechSynthesis API. Please use a recent version of Chrome, Edge, or Safari.';
4
5function populateVoices() {
6  voices = speechSynthesis.getVoices().sort((a, b) => a.lang.localeCompare(b.lang));
7}
8
9// Initial population of voices
10if ('speechSynthesis' in window) {
11  populateVoices();
12  window.speechSynthesis.onvoiceschanged = populateVoices;
13}
14
15function pickVoiceForLang(lang) {
16  if (!lang || lang === 'auto') return null;
17  return (
18    voices.find(v => v.lang.toLowerCase() === lang.toLowerCase()) ||
19    voices.find(v => v.lang.toLowerCase().startsWith(lang.split('-')[0].toLowerCase()))
20  );
21}
22
23/** =============
24 * 普通朗读
25 * ============= */
26function speak(text, lang, voiceName, rate, pitch) {
27  if (!text) return;
28  speechSynthesis.cancel();
29  utterance = new SpeechSynthesisUtterance(text);
30
31  if (lang !== 'auto') utterance.lang = lang;
32
33  let v = null;
34  if (voiceName) {
35    v = voices.find(x => x.name === voiceName);
36  } else if (lang !== 'auto') {
37    v = pickVoiceForLang(lang);
38  }
39  if (v) utterance.voice = v;
40
41  utterance.rate = parseFloat(rate) || 1;
42  utterance.pitch = parseFloat(pitch) || 1;
43  speechSynthesis.speak(utterance);
44}
45
46/** =======================================
47 * 🚀 老年男声模式(高级朗读 + 停顿 + 深沉声音)
48 * ======================================= */
49function speakElder(rawText) {
50  if (!rawText || rawText.trim() === '') return; // 增加对空文本的检查
51
52  speechSynthesis.cancel();
53
54  // 将文本分句,增强自然停顿感
55  const parts = rawText
56    .replace(/\r?\n|\r/g, ' ') // 统一处理不同操作系统的换行符,替换为单个空格
57    .split(/[。!?.!?]/) // 修改分句逻辑,不保留标点符号
58    .filter(part => part.trim() !== ''); // 过滤掉空字符串,确保没有多余的空格或空句
59
60  // 优化男声关键词,增加更多匹配项
61  const maleKeywords = /(male|man|maged|tarik|daniel|mark|hassan|naayf|david|paul|steve|george|ryan|alex|tom|john|peter|robert|william|james|charles|thomas|joseph|christopher|daniel|paul|mark|donald|kenneth|anthony|kevin|jason|matthew|gary|timothy|larry|jeffrey|frank|scott|eric|stephen|andrew|raymond|gregory|joshua|dennis|walter|patrick|peter|harold|douglas|henry|carl|arthur|ralph|bruce|jeremy|terry|randy|louis|russell|eugene|bobby|fred|wayne|billy|howard|phillip|gordon|ronald|jose|franklin|harvey|earl|jerry|dennis|larry|walter|patrick|peter|harold|douglas|henry|carl|arthur|ralph|bruce|jeremy|terry|randy|louis|russell|eugene|bobby|fred|wayne|billy|howard|phillip|gordon|ronald|jose|franklin|harvey|earl|jerry)/i;
62
63  let maleVoice = voices.find(v => maleKeywords.test(v.name)) || null;
64
65  // 如果没有找到匹配的男性声音,尝试使用第一个可用的声音作为备用
66  if (!maleVoice && voices.length > 0) {
67    maleVoice = voices[0];
68    console.warn('未找到匹配的男性声音,使用第一个可用声音作为备用。');
69  }
70
71  let index = 0;
72
73  function readNext() {
74    if (index >= parts.length) return;
75
76    const text = parts[index];
77    const u = new SpeechSynthesisUtterance(text);
78
79    // 使用男性声音
80    if (maleVoice) u.voice = maleVoice;
81
82    u.rate = 1; // 老年语速偏慢
83    u.pitch = 0.6; // 老年声音偏低沉
84    u.volume = 1;
85
86    // 停顿处理(匹配句号、逗号等)
87    let pause = 300;
88    if (/[。.!?]/.test(text)) pause = 600;
89    if (/,/.test(text)) pause = 450;
90
91    u.onend = function() {
92      index++;
93      setTimeout(readNext, pause);
94    };
95
96    speechSynthesis.speak(u);
97  }
98
99  readNext();
100}
101
102/**
103 * 从 HTML 字符串中提取纯文本内容
104 * @param {string} htmlString - 包含 HTML 标签的字符串
105 * @returns {string} 提取出的纯文本内容
106 */
107function extractTextFromHtml(htmlString) {
108  const tempDiv = document.createElement('div');
109  tempDiv.innerHTML = htmlString;
110  return tempDiv.innerText;
111}
112
113/**
114 * 抽离出来的公共朗读方法
115 * @param {string} content - 要朗读的文案或 HTML 字符串
116 * @param {string} lang - 语言,例如 'zh-CN' (简体中文), 'zh-TW' (繁体中文), 'en-US' (美式英语), 'en-GB' (英式英语), 'de-DE' (德语), 'es-ES' (西班牙语), 'fr-FR' (法语), 'it-IT' (意大利语), 'ja-JP' (日语), 'pt-PT' (葡萄牙语), 'ru-RU' (俄语), 'ar-SA' (阿拉伯语), 'ko-KR' (韩语) 等
117 * @param {string} voiceType - 音色类型,'elder' 为老年男声模式,'default' 为普通模式
118 * @param {string} contentType - 内容类型,'plain' 为纯文本,'html' 为 HTML 字符串 (默认为 'plain')
119 * @param {string} voiceName - 具体的语音名称 (可选, 仅在 voiceType 为 'default' 时有效)
120 * @param {number} rate - 语速 (可选, 仅在 voiceType 为 'default' 时有效)
121 * @param {number} pitch - 音调 (可选, 仅在 voiceType 为 'default' 时有效)
122 */
123export function readTextWithConfig(content, lang, voiceType = 'elder', contentType = 'plain', voiceName = '', rate = 1, pitch = 1) {
124  if (!('speechSynthesis' in window)) {
125    alert(speechNoticeText);
126    return;
127  }
128
129  let textToSpeak = content;
130  if (contentType === 'html') {
131    textToSpeak = extractTextFromHtml(content);
132  }
133
134  if (!textToSpeak || textToSpeak.trim() === '') {
135    console.warn('没有可朗读的内容');
136    return;
137  }
138
139  if (voiceType === 'elder') {
140    speakElder(textToSpeak);
141  } else {
142    speak(textToSpeak, lang, voiceName, rate, pitch);
143  }
144}
145
146/**
147 * 暂停当前正在进行的语音朗读。
148 */
149export function pauseSpeech() {
150  speechSynthesis.pause();
151}
152
153/**
154 * 继续之前暂停的语音朗读。
155 */
156export function resumeSpeech() {
157  speechSynthesis.resume();
158}
159
160/**
161 * 停止当前正在进行的语音朗读,并取消所有待处理的朗读任务。
162 */
163export function stopSpeech() {
164  speechSynthesis.cancel();
165}
166
167/**
168 * 获取浏览器支持的所有可用语音列表。
169 * @returns {SpeechSynthesisVoice[]} 可用的语音列表数组。
170 */
171export function getAvailableVoices() {
172  return voices;
173}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.