VolumeTTS.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. class VolumeTTS {
  2. // 默认TTS参数
  3. static DEFAULT_PARAMS = {
  4. lang: "zh-CN",
  5. speed: 1,
  6. pitch: 1,
  7. volume: 1,
  8. };
  9. // 数字到中文的映射
  10. static DIGIT_MAP = {
  11. 0: "零", 1: "一", 2: "二", 3: "三", 4: "四",
  12. 5: "五", 6: "六", 7: "七", 8: "八", 9: "九"
  13. };
  14. constructor(opts = {}) {
  15. this.ttsParams = this._initializeParams(opts);
  16. this.TTSModule = null;
  17. this.customOpts = null;
  18. this.platform = uni.getSystemInfoSync().platform;
  19. this.isInitialized = false;
  20. this.isSpeaking = false;
  21. // #ifdef APP-PLUS
  22. this._initTTS();
  23. // #endif
  24. }
  25. /**
  26. * 初始化TTS参数
  27. * @private
  28. */
  29. _initializeParams(opts) {
  30. const audioConfig = uni.getStorageSync("audioConfig");
  31. if (opts && Object.keys(opts).length > 0) {
  32. return { ...VolumeTTS.DEFAULT_PARAMS, ...opts };
  33. }
  34. if (audioConfig) {
  35. return {
  36. lang: "zh-CN",
  37. speed: Math.max(0.1, Math.min(2.0, audioConfig.audioSpeed / 10)),
  38. pitch: Math.max(0.1, Math.min(2.0, audioConfig.audioVolume)),
  39. volume: 1,
  40. };
  41. }
  42. return { ...VolumeTTS.DEFAULT_PARAMS };
  43. }
  44. /**
  45. * 初始化TTS模块
  46. * @private
  47. */
  48. _initTTS() {
  49. try {
  50. this.TTSModule = uni.requireNativePlugin("nrb-tts-plugin");
  51. if (!this.TTSModule) {
  52. console.warn('TTS模块加载失败');
  53. return;
  54. }
  55. this.TTSModule.init(
  56. {
  57. lang: "zh",
  58. country: "CN",
  59. },
  60. (res) => {
  61. if (res.success === 0) {
  62. this.isInitialized = true;
  63. console.log('TTS初始化成功');
  64. // 移除调试弹窗,改为控制台日志
  65. } else {
  66. console.error('TTS初始化失败:', res);
  67. }
  68. }
  69. );
  70. } catch (error) {
  71. console.error('TTS模块初始化异常:', error);
  72. }
  73. }
  74. /**
  75. * 预处理文本,将数字转换为中文
  76. * @param {string} text - 待处理的文本
  77. * @returns {string} 处理后的文本
  78. */
  79. preprocessText(text) {
  80. if (!text || typeof text !== 'string') {
  81. return '';
  82. }
  83. return text.replace(/\d+/g, (match) => {
  84. return match
  85. .split("")
  86. .map((digit) => VolumeTTS.DIGIT_MAP[digit] || digit)
  87. .join("");
  88. });
  89. }
  90. /**
  91. * 设置自定义的opts参数
  92. * @param {Object} opts - 自定义的opts参数
  93. */
  94. setCustomOpts(opts) {
  95. if (opts && typeof opts === 'object') {
  96. this.customOpts = { ...opts };
  97. }
  98. }
  99. /**
  100. * 获取当前的自定义opts参数
  101. * @returns {Object|null} 当前的自定义opts参数
  102. */
  103. getCustomOpts() {
  104. return this.customOpts ? { ...this.customOpts } : null;
  105. }
  106. /**
  107. * 清除自定义的opts参数,恢复默认行为
  108. */
  109. clearCustomOpts() {
  110. this.customOpts = null;
  111. }
  112. /**
  113. * 获取平台特定的TTS选项
  114. * @private
  115. */
  116. _getPlatformOpts() {
  117. if (this.platform === "ios") {
  118. return {
  119. rate: this.ttsParams.speed,
  120. lang: "zh-CN",
  121. volume: this.ttsParams.volume,
  122. };
  123. } else {
  124. return {
  125. pitch: this.ttsParams.pitch,
  126. speechRate: this.ttsParams.speed,
  127. queueMode: 1,
  128. };
  129. }
  130. }
  131. /**
  132. * 语音合成
  133. * @param {string} text - 要合成的文本
  134. * @param {Object} customOpts - 自定义选项
  135. * @returns {Promise<boolean>} 是否成功开始合成
  136. */
  137. async speak(text, customOpts = null) {
  138. if (!text || typeof text !== 'string') {
  139. console.warn('TTS: 无效的文本输入');
  140. return false;
  141. }
  142. if (this.isSpeaking) {
  143. console.log('TTS: 正在播放中,停止当前播放');
  144. this.stop();
  145. }
  146. const processedText = this.preprocessText(text);
  147. console.log('TTS: 开始合成文本:', processedText);
  148. // #ifdef H5
  149. return this._speakH5(processedText);
  150. // #endif
  151. // #ifdef APP-PLUS
  152. return this._speakApp(processedText, customOpts);
  153. // #endif
  154. return false;
  155. }
  156. /**
  157. * H5端语音合成
  158. * @private
  159. */
  160. _speakH5(text) {
  161. if ("speechSynthesis" in window) {
  162. try {
  163. const utterance = new SpeechSynthesisUtterance(text);
  164. utterance.lang = this.ttsParams.lang;
  165. utterance.rate = this.ttsParams.speed;
  166. utterance.pitch = this.ttsParams.pitch;
  167. utterance.volume = this.ttsParams.volume;
  168. utterance.onstart = () => {
  169. this.isSpeaking = true;
  170. console.log('TTS: H5开始播放');
  171. };
  172. utterance.onend = () => {
  173. this.isSpeaking = false;
  174. console.log('TTS: H5播放完成');
  175. };
  176. utterance.onerror = (error) => {
  177. this.isSpeaking = false;
  178. console.error('TTS: H5播放错误:', error);
  179. };
  180. speechSynthesis.speak(utterance);
  181. return true;
  182. } catch (error) {
  183. console.error('TTS: H5播放异常:', error);
  184. return false;
  185. }
  186. }
  187. return false;
  188. }
  189. /**
  190. * APP端语音合成
  191. * @private
  192. */
  193. _speakApp(text, customOpts) {
  194. if (!this.TTSModule || Object.keys(this.TTSModule).length === 0) {
  195. console.log('TTS: 重新初始化模块');
  196. this._initTTS();
  197. return false;
  198. }
  199. try {
  200. let opts;
  201. // 优先级:方法参数传入的customOpts > setCustomOpts设置的customOpts > 默认opts
  202. if (customOpts && typeof customOpts === 'object') {
  203. opts = customOpts;
  204. } else if (this.customOpts) {
  205. opts = this.customOpts;
  206. } else {
  207. opts = this._getPlatformOpts();
  208. }
  209. this.TTSModule.speak(text, opts, (result) => {
  210. if (result && result.success === 0) {
  211. this.isSpeaking = true;
  212. console.log('TTS: APP开始播放');
  213. } else {
  214. console.error('TTS: APP播放失败:', result);
  215. }
  216. });
  217. return true;
  218. } catch (error) {
  219. console.error('TTS: APP播放异常:', error);
  220. return false;
  221. }
  222. }
  223. /**
  224. * 停止语音合成
  225. */
  226. stop() {
  227. this.isSpeaking = false;
  228. // #ifdef H5
  229. if ("speechSynthesis" in window) {
  230. speechSynthesis.cancel();
  231. }
  232. // #endif
  233. // #ifdef APP-PLUS
  234. if (this.TTSModule && typeof this.TTSModule.stop === 'function') {
  235. try {
  236. this.TTSModule.stop();
  237. } catch (error) {
  238. console.error('TTS: 停止播放异常:', error);
  239. }
  240. }
  241. // #endif
  242. console.log('TTS: 已停止播放');
  243. }
  244. /**
  245. * 暂停语音合成
  246. */
  247. pause() {
  248. // #ifdef H5
  249. if ("speechSynthesis" in window) {
  250. speechSynthesis.pause();
  251. }
  252. // #endif
  253. console.log('TTS: 已暂停播放');
  254. }
  255. /**
  256. * 恢复语音合成
  257. */
  258. resume() {
  259. // #ifdef H5
  260. if ("speechSynthesis" in window) {
  261. speechSynthesis.resume();
  262. }
  263. // #endif
  264. console.log('TTS: 已恢复播放');
  265. }
  266. /**
  267. * 检查是否正在播放
  268. * @returns {boolean} 是否正在播放
  269. */
  270. isPlaying() {
  271. return this.isSpeaking;
  272. }
  273. /**
  274. * 检查TTS是否已初始化
  275. * @returns {boolean} 是否已初始化
  276. */
  277. isReady() {
  278. return this.isInitialized;
  279. }
  280. /**
  281. * 销毁TTS实例
  282. */
  283. destroy() {
  284. this.stop();
  285. this.isInitialized = false;
  286. this.TTSModule = null;
  287. this.customOpts = null;
  288. this.ttsParams = { ...VolumeTTS.DEFAULT_PARAMS };
  289. console.log('TTS: 实例已销毁');
  290. }
  291. /**
  292. * 更新TTS参数
  293. * @param {Object} newParams - 新的参数
  294. */
  295. updateParams(newParams) {
  296. if (newParams && typeof newParams === 'object') {
  297. this.ttsParams = { ...this.ttsParams, ...newParams };
  298. console.log('TTS: 参数已更新:', this.ttsParams);
  299. }
  300. }
  301. /**
  302. * 获取当前TTS参数
  303. * @returns {Object} 当前TTS参数
  304. */
  305. getParams() {
  306. return { ...this.ttsParams };
  307. }
  308. }
  309. export default VolumeTTS;