UNPKG

@minto-ai/huoshan-mp-tts

Version:

借助“火山引擎在线语音合成API”实现微信小程序端“文本转语音

981 lines (980 loc) 30.8 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); class EventBus { constructor() { __publicField(this, "listeners", /* @__PURE__ */ new Map()); } on(eventName, callback) { const callbacks = this.listeners.get(eventName); if (callbacks) { callbacks.push(callback); } else { this.listeners.set(eventName, [callback]); } } emit(eventName, data) { const callbacks = this.listeners.get(eventName); if (callbacks) { callbacks.forEach((callback) => { callback(data); }); } } } function createEventBus() { return new EventBus(); } function getUuid() { const timestamp = Date.now().toString(36); const randomValue = Math.floor(Math.random() * 2 ** 32).toString(36); return timestamp + randomValue; } function objectToQueryString(obj) { return Object.entries(obj).map(([key, value]) => { return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; }).join("&"); } class SsmlConverter { /** * 创建 Ssml 转换器 * @param pronunciationRules 多音字/词规则列表,按长度降序用于最长匹配 */ constructor(pronunciationRules) { __publicField(this, "pronunciationRules"); this.pronunciationRules = [ ...pronunciationRules.sort((a, b) => b.content.length - a.content.length) ]; } /** * 批量转换段落为 Ssml 字符串数组 * @param paragraphs 段落文本数组 * @returns Ssml 字符串数组 */ convertParagraphs(paragraphs) { const ssmlList = []; for (const paragraph of paragraphs) { ssmlList.push(this.convertParagraph(paragraph)); } return ssmlList; } /** * 单段落转换为 Ssml * @param paragraph 段落文本 * @returns Ssml 字符串 */ convertParagraph(paragraph) { let ssmlText = ""; let cursor = 0; while (cursor < paragraph.length) { const matchedRule = this.pronunciationRules.find( (rule) => paragraph.startsWith(rule.content, cursor) ) ?? null; if (matchedRule !== null) { ssmlText += `<phoneme alphabet="${matchedRule.alphabet}" ph="${matchedRule.ph}">${matchedRule.content}</phoneme>`; cursor += matchedRule.content.length; } else { ssmlText += paragraph[cursor]; cursor += 1; } } return `<speak>${ssmlText}</speak>`; } } let ssmlConverter = null; function createSsmlConverter(pronunciationRules) { if (ssmlConverter === null) { ssmlConverter = new SsmlConverter(pronunciationRules); } return ssmlConverter; } class LifeHandler { /** * 处理器被激活(首次激活触发) */ onActive() { } /** * 处理器首次执行任务之前 */ onBeforeFirstExecute() { } /** * 处理器执行任务完成 */ onCompleted() { } /** * 处理器执被销毁 */ onFinish() { } } class BaseHandler extends LifeHandler { constructor() { super(...arguments); __publicField(this, "nextHandler", null); __publicField(this, "executeController", null); __publicField(this, "isFirstExecute", true); __publicField(this, "isHandleDataAcceptedComplete", false); } setExecuteController(executeController) { this.executeController = executeController; } linkHandler(nextHandler) { this.nextHandler = nextHandler; } get isLastHandler() { return !this.nextHandler; } forwardToHandler(result) { if (this.nextHandler) { this.nextHandler.handle(result); } } setHandlerStatus(status) { this.handlerStatus = status; } equalHandlerStatus(status) { return this.handlerStatus === status; } } var SerialHandlerStatus = /* @__PURE__ */ ((SerialHandlerStatus2) => { SerialHandlerStatus2["OFFLINE"] = "offline"; SerialHandlerStatus2["ACTIVE"] = "active"; SerialHandlerStatus2["PENDING"] = "pending"; SerialHandlerStatus2["EXECUTING"] = "executing"; SerialHandlerStatus2["COMPLETED"] = "completed"; SerialHandlerStatus2["FINISH"] = "finish"; return SerialHandlerStatus2; })(SerialHandlerStatus || {}); var SerialTaskItemStatus = /* @__PURE__ */ ((SerialTaskItemStatus2) => { SerialTaskItemStatus2["PENDING"] = "pending"; SerialTaskItemStatus2["EXECUTING"] = "executing"; SerialTaskItemStatus2["COMPLETED"] = "completed"; return SerialTaskItemStatus2; })(SerialTaskItemStatus || {}); class SerialHandler extends BaseHandler { constructor() { super(); /** * 待处理任务队列 */ __publicField(this, "taskQueue", []); /** * 当前处理器状态 */ __publicField(this, "handlerStatus", SerialHandlerStatus.OFFLINE); } /** * 获取待处理任务数量 */ get taskQueueLength() { return this.taskQueue.length; } /** * 检查任务执行条件 * 子类可重写此方法,实现自定义的任务执行前置条件检查 * * @returns {boolean} - 当满足执行条件时返回 true,否则返回 false */ executePreCheck() { return true; } /** * 触发处理器激活行为(空闲状态 => 激活状态) */ triggerHandlerActive() { this.setHandlerStatus(SerialHandlerStatus.ACTIVE); this.onActive(); this.triggerHandlerPending(); } /** * 触发处理器待机行为(激活状态 => 待执行状态 执行状态 => 待执行状态) */ triggerHandlerPending() { this.setHandlerStatus(SerialHandlerStatus.PENDING); } /** * 触发处理器执行行为(激活状态 => 执行状态) */ triggerHandlerExecute() { this.setHandlerStatus(SerialHandlerStatus.EXECUTING); const taskItem = this.taskQueue.shift(); Promise.resolve().then(() => { const { isFirstExecute } = this; if (isFirstExecute) { this.isFirstExecute = false; this.onBeforeFirstExecute(); } let isLastExecute = false; if (this.isHandleDataAcceptedComplete && !this.taskQueueLength) { isLastExecute = true; } const context = { taskItem, isFirstExecute, isLastExecute }; this.execute(context); }); } /** * 触发处理器成功行为(执行状态 => 成功状态) */ triggerHandlerCompleted() { this.setHandlerStatus(SerialHandlerStatus.COMPLETED); if (this.nextHandler) { this.nextHandler.handle(null); this.nextHandler.isHandleDataAcceptedComplete = true; } this.onCompleted(); } /** * 触发处理器销毁行为(待机状态 => 销毁状态) */ triggerHandlerFinish() { this.taskQueue = []; this.isFirstExecute = true; this.isHandleDataAcceptedComplete = false; this.setHandlerStatus(SerialHandlerStatus.FINISH); this.onFinish(); } /** * 触发应用被销毁行为 */ triggerAppFinish() { var _a; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_appFinish"); } /** * 任务完成后的回调函数。 */ taskCompletedCallback() { if (this.taskQueueLength) { this.triggerHandlerExecute(); } else { if (this.isHandleDataAcceptedComplete) { this.triggerHandlerCompleted(); if (this.isLastHandler) { this.triggerAppFinish(); } } else { this.triggerHandlerPending(); } } } handle(original) { this.taskQueue.push({ uuid: getUuid(), original, status: SerialTaskItemStatus.PENDING }); if (!this.executePreCheck()) { this.triggerHandlerPending(); return; } if (this.equalHandlerStatus(SerialHandlerStatus.PENDING)) { this.triggerHandlerExecute(); } } } const _AudioActuator = class _AudioActuator extends SerialHandler { constructor() { super(...arguments); __publicField(this, "audioContext", wx.createWebAudioContext()); __publicField(this, "bufferSource", null); __publicField(this, "gainNode", null); __publicField(this, "volume", 1); } onActive() { if (!this.audioContext) { this.audioContext = wx.createWebAudioContext(); this.audioContext.resume(); } } execute(context) { if (context.isLastExecute) { this.taskCompletedCallback(); return; } this.bufferSource = this.audioContext.createBufferSource(); this.bufferSource.buffer = context.taskItem.original; this.gainNode = this.audioContext.createGain(); this.bufferSource.connect(this.gainNode); this.gainNode.connect(this.audioContext.destination); this.gainNode.gain.setValueAtTime(this.volume, this.audioContext.currentTime); this.gainNode.gain.linearRampToValueAtTime(1, this.audioContext.currentTime + _AudioActuator.FADE_DURATION); this.gainNode.gain.setValueAtTime(1, this.audioContext.currentTime + context.taskItem.original.duration - _AudioActuator.FADE_DURATION); this.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + context.taskItem.original.duration); this.bufferSource.start(); this.bufferSource.onended = () => { this.taskCompletedCallback(); }; } onFinish() { var _a; if (this.bufferSource) { this.bufferSource.stop(); this.bufferSource = null; } if (this.gainNode) { this.gainNode.disconnect(); this.gainNode = null; } if (this.audioContext) { this.audioContext.close(); this.audioContext = null; } (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_audioActuatorFinish"); } onBeforeFirstExecute() { var _a; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_audioActuatorBeforeFirstExecute"); } }; __publicField(_AudioActuator, "FADE_DURATION", 0.018); let AudioActuator = _AudioActuator; const _ByteBuffer = class _ByteBuffer extends SerialHandler { constructor() { super(...arguments); __publicField(this, "lastRemainingByte", new Uint8Array([])); } execute(context) { if (context.isLastExecute) { const originalByte = new Uint8Array([...this.lastRemainingByte]); if (originalByte.length > _ByteBuffer.MIN_LOAD_SIZE) { this.forwardToHandler(originalByte); } this.lastRemainingByte = new Uint8Array([]); this.taskCompletedCallback(); } else { const originalByte = new Uint8Array([...this.lastRemainingByte, ...context.taskItem.original]); if (originalByte.length >= _ByteBuffer.MIN_BYTE_SIZE) { this.forwardToHandler(originalByte); this.lastRemainingByte = new Uint8Array([]); } else { this.lastRemainingByte = originalByte; } this.taskCompletedCallback(); } } onFinish() { var _a; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_byteBufferFinish"); this.lastRemainingByte = new Uint8Array([]); } }; __publicField(_ByteBuffer, "MIN_BYTE_SIZE", 60 * 1024); __publicField(_ByteBuffer, "MIN_LOAD_SIZE", 0.1 * 1024); let ByteBuffer = _ByteBuffer; class CreateSsml extends SerialHandler { constructor() { super(...arguments); __publicField(this, "ssmlConverter"); } /** * 初始化属性 * @param ssmlConfig SSML 配置(包含发音规则) */ initProperty(ssmlConfig) { this.ssmlConverter = createSsmlConverter(ssmlConfig.pronunciationRules ?? []); } /** * 执行处理,将当前文本转换为 SSML 并传递到下一个处理器 * @param context 串行任务上下文 */ execute(context) { if (context.isLastExecute) { this.taskCompletedCallback(); } else { this.ssmlConverter.convertParagraphs([context.taskItem.original]).forEach((paragraph) => { this.forwardToHandler(paragraph); }); this.taskCompletedCallback(); } } /** * 完成回调,广播文本切割结束事件 */ onFinish() { var _a; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_createSsmlFinish"); } } class DecodeData extends SerialHandler { constructor() { super(...arguments); __publicField(this, "audioContext", wx.createWebAudioContext()); } onActive() { if (!this.audioContext) { this.audioContext = wx.createWebAudioContext(); this.audioContext.resume(); } } execute(context) { if (context.isLastExecute) { this.taskCompletedCallback(); return; } const arrayBuffer = context.taskItem.original.buffer; if (!arrayBuffer) { this.taskCompletedCallback(); return; } this.audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => { this.forwardToHandler(audioBuffer); this.taskCompletedCallback(); }, () => { this.forwardToHandler(this.audioContext.createBuffer(1, 1, 1)); this.taskCompletedCallback(); }); } onFinish() { var _a; if (this.audioContext) { this.audioContext.close(); this.audioContext = null; } (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_decodeDataFinish"); } } var o = Object.defineProperty; var I = (r, t, e) => t in r ? o(r, t, { enumerable: true, configurable: true, writable: true, value: e }) : r[t] = e; var i = (r, t, e) => I(r, typeof t != "symbol" ? t + "" : t, e); var h = /* @__PURE__ */ ((r) => (r.SPACE_CHAR = "spaceChar", r.CHINESE_CHAR = "chineseChar", r.CHINESE_PUNCTUATION = "chinesePunctuation", r.ENGLISH_WORD = "englishWord", r.ENGLISH_PUNCTUATION = "englishPunctuation", r.NUMBER = "number", r.UNKNOWN_CHAR = "unknownChar", r))(h || {}); class d { constructor() { i(this, "CHINESE_CHAR_REGEX", /[\u4E00-\u9FA5]/); i(this, "CHINESE_PUNCTUATION_REGEX", /[,。!?;:、"'()—…·]/); i(this, "CHINESE_PARAGRAPH_END_REGEX", /[。!?;]/); i(this, "ENGLISH_WORD_START_REGEX", /[a-z]/i); i(this, "ENGLISH_WORD_CONTINUE_REGEX", /[a-z'’-]/i); i(this, "ENGLISH_PUNCTUATION_REGEX", /[.,!?;:'"()-]/); i(this, "ENGLISH_PARAGRAPH_END_REGEX", /[.!?;]/); i(this, "SPACE_CHAR_REGEX", /\s/); i(this, "DIGIT_REGEX", /\d/); i(this, "DECIMAL_REGEX", /[\d.]/); i(this, "TRAILING_CLOSER_REGEX", /[)"'\]}】》』」’”]/); i(this, "MIN_PARAGRAPH_LENGTH", 10); i(this, "pendingTokens", []); } /** * 将输入文本转换为文本标记。 * * 标记化规则: * - 中文字符:单个字符作为一个标记 * - 中文标点:单个标点作为一个标记 * - 英文单词:连续英文字母,支持缩写撇号('’)和连字符(-) * - 数字序列:支持小数点,如 22.5 作为一个完整数字标记 * - 空白字符:空格、制表符等作为一个标记 * - 其他字符:作为未知类型标记 * * @param text 原始文本 * @returns 标记化后的文本标记数组 */ textToTokens(t) { const e = []; let n = 0; for (; n < t.length; ) { const s = t[n]; if (this.CHINESE_CHAR_REGEX.test(s)) e.push({ type: h.CHINESE_CHAR, value: s }); else if (this.DIGIT_REGEX.test(s)) { const l = n; let a = n + 1; for (; a < t.length && this.DECIMAL_REGEX.test(t[a]); ) a++; const T = t.substring(l, a); e.push({ type: h.NUMBER, value: T }), n += T.length - 1; } else if (this.ENGLISH_WORD_START_REGEX.test(s)) { const l = n; let a = n + 1; for (; a < t.length && this.ENGLISH_WORD_CONTINUE_REGEX.test(t[a]); ) a++; const T = t.substring(l, a); e.push({ type: h.ENGLISH_WORD, value: T }), n += T.length - 1; } else this.CHINESE_PUNCTUATION_REGEX.test(s) ? e.push({ type: h.CHINESE_PUNCTUATION, value: s }) : this.ENGLISH_PUNCTUATION_REGEX.test(s) ? e.push({ type: h.ENGLISH_PUNCTUATION, value: s }) : this.SPACE_CHAR_REGEX.test(s) ? e.push({ type: h.SPACE_CHAR, value: s }) : e.push({ type: h.UNKNOWN_CHAR, value: s }); n++; } return e; } /** * 文本标记转换为段落,并更新 pendingTokens。 * * 跨次调用缓存逻辑: * 1. 先将前次剩余标记(pendingTokens)与本次新标记拼接 * 2. 对拼接后的完整标记序列进行切分判断 * 3. 未达切分条件的标记缓存到 pendingTokens 供下次使用 * * @param tokens 文本标记数组 * @returns 段落数组(每段为 Token[]) */ tokenToParagraphs(t) { const e = this.pendingTokens.length > 0 ? [...this.pendingTokens, ...t] : [...t]; this.pendingTokens = []; const n = []; let s = [], l = 0; for (; l < e.length; ) { const a = e[l]; if (s.push(a), s.length >= this.MIN_PARAGRAPH_LENGTH && (this.CHINESE_PARAGRAPH_END_REGEX.test(a.value) || this.ENGLISH_PARAGRAPH_END_REGEX.test(a.value))) { let T = l + 1; for (; T < e.length; ) { const g = e[T]; if (g.type === h.SPACE_CHAR || this.TRAILING_CLOSER_REGEX.test(g.value)) s.push(g), T++; else break; } n.push([...s]), s = [], l = T - 1; } l += 1; } return s.length > 0 && (this.pendingTokens = [...s]), n; } /** * 根据输入文本构建段落列表,并在必要时输出剩余标记。 * * @param text 输入文本 * @param includeRemaining 是否包含待处理的剩余标记 * @returns 段落数组(每段为字符串) */ build(t, e) { const n = this.textToTokens(t), s = this.tokenToParagraphs(n); return e && this.pendingTokens.length > 0 && (s.push([...this.pendingTokens]), this.pendingTokens = []), s.map((a) => a.map((T) => T.value).join("")); } /** * 重置段落构建器,清空内部 pendingTokens 缓存。 * 用于单例模式下重复调用时确保状态干净。 */ reset() { this.pendingTokens = []; } } let E = null; function N() { return E !== null ? (E.reset(), E) : (E = new d(), E); } class A { constructor() { i(this, "pendingText", ""); i(this, "MD_LINK_TAIL_PATTERNS", [/\[$/, /\[[^\]]*$/, /\[[^\]]*\]$/, /\[[^\]]*\]\($/, /\[[^\]]*\]\([^)]*$/, /\[[^\]]*\]\([^)]*\)$/]); i(this, "NUMBER_TAIL_PATTERNS", [/\d+$/, /\d+\.$/, /\d+\.\d+$/]); i(this, "IMG_TAG_TAIL_PATTERNS", [/((<)|(<i)|(<im)|(<img))$/, /<img$/, /<img[^>]*$/, /<img[^>]*>$/, /<img[^>]*\/$/, /<img[^>]*\/>$/]); i(this, "VIDEO_TAG_TAIL_PATTERNS", [/((<)|(<v)|(<vi)|(<vid)|(<vide)|(<video))$/, /((<\/v)|(<\/vi)|(<\/vid)|(<\/vide)|(<\/video))$/, /<video$/, /<video[^>]*$/, /<video[^>]*>$/, /<video[^>]*><\/$/, /<video[^>]*><\/video$/, /<video[^>]*><\/video>$/]); } /** * 判断片段末尾是否处在 Markdown 链接结构中(未完成或完整)。 * * @param text 输入文本。 * @returns 是否在链接尾部。 */ isMarkdownLinkTail(t) { return this.MD_LINK_TAIL_PATTERNS.some((e) => e.test(t)); } /** * 判断片段末尾是否是数字或小数的一部分(未完成或完整)。 * * @param text 输入文本。 * @returns 是否命中数字尾部。 */ isNumberTail(t) { return this.NUMBER_TAIL_PATTERNS.some((e) => e.test(t)); } /** * 判断片段末尾是否处在 CSS img 标签结构中(未完成或完整)。 * * @param text 输入文本。 * @returns 是否命中 img 标签尾部。 */ isImgTagTail(t) { return this.IMG_TAG_TAIL_PATTERNS.some((e) => e.test(t)); } /** * 判断片段末尾是否处在 HTML video 标签结构中(未完成或完整)。 * * @param text 输入文本。 * @returns 是否命中 video 标签尾部。 */ isVideoTagTail(t) { return this.VIDEO_TAG_TAIL_PATTERNS.some((e) => e.test(t)); } /** * 合并缓存并判断是否需要继续缓存当前片段,以避免在未完成结构处切句。 * * @param text 当前输入片段。 * @param includeRemaining 是否包含待处理的剩余文本。 * @returns 预处理结果:若触发缓存则返回空字符串与新的缓存;否则返回合并后的文本与空缓存。 */ preprocess(t, e) { let n = ""; return this.pendingText.length > 0 ? n = `${this.pendingText}${t}` : n = t, e ? (this.pendingText = "", { processedText: n, nextPendingText: "" }) : this.isMarkdownLinkTail(n) ? (this.pendingText = n, { processedText: "", nextPendingText: n }) : this.isNumberTail(n) ? (this.pendingText = n, { processedText: "", nextPendingText: n }) : this.isImgTagTail(n) ? (this.pendingText = n, { processedText: "", nextPendingText: n }) : this.isVideoTagTail(n) ? (this.pendingText = n, { processedText: "", nextPendingText: n }) : (this.pendingText = "", { processedText: n, nextPendingText: "" }); } } let _ = null; function R() { return _ !== null || (_ = new A()), _; } class P { /** * 执行语义替换。 * @param {string} text 输入文本 * @returns {string} 替换后的文本 */ replace(t) { let e = t; return e = e.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1"), e = e.replace(/<img[^>]*\/>/g, ""), e = e.replace(/<img[^>]*>/g, ""), e = e.replace(/<video[^>]*><\/video\s*>/g, ""), e; } } let p = null; function G() { return p !== null || (p = new P()), p; } class x { /** * 清洗段落数组。 * * 处理流程: * 1. 去除每段首尾空白字符; * 2. 过滤空字符串; * 3. 过滤仅含标点/符号/空白而无字母、数字或中文的“无意义段”。 * * @param paragraphs 原始段落数组 * @returns 清洗后的段落数组 */ sanitize(t) { return t.map((e) => e.trim()).filter((e) => e.length > 0).filter((e) => /[\p{L}\p{N}]/u.test(e)); } } let u = null; function C() { return u !== null || (u = new x()), u; } class S { /** * 清洗输入文本,按需移除特定字符。 * * @param text 输入文本 * @returns 规范化后的文本 */ sanitize(t) { let e = t; return e = e.replace(/\*+/g, ""), e = e.replace(/#+/g, ""), e = e.replace(/[\u{1F000}-\u{1FAFF}]/gu, ""), e; } } let c = null; function H() { return c !== null || (c = new S()), c; } class f { constructor() { i(this, "sanitizeTextInstance", H()); i(this, "preprocessTextInstance", R()); i(this, "replaceTextInstance", G()); i(this, "paragraphsInstance", N()); i(this, "sanitizeParagraphsInstance", C()); } /** * 同步处理输入文本片段,返回已分段的文本数组 * * @param text 本次输入的文本片段 * @param includeRemaining 是否已无后续文本 * @returns 分段后的段落数组 */ processText(t, e = false) { const n = this.sanitizeTextInstance.sanitize(t), { processedText: s } = this.preprocessTextInstance.preprocess(n, e), l = this.replaceTextInstance.replace(s), a = this.paragraphsInstance.build(l, e); return this.sanitizeParagraphsInstance.sanitize(a); } } class TextSplit extends SerialHandler { constructor() { super(...arguments); __publicField(this, "textStreamSlicer", new f()); } execute(context) { if (context.isLastExecute) { this.textStreamSlicer.processText("", true).forEach((paragraph) => { this.forwardToHandler(paragraph); }); } else { this.textStreamSlicer.processText(context.taskItem.original).forEach((paragraph) => { this.forwardToHandler(paragraph); }); } this.taskCompletedCallback(); } onFinish() { var _a; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_textSplitFinish"); } } class TtsRequest extends SerialHandler { constructor() { super(...arguments); __publicField(this, "webSocketInstance", null); __publicField(this, "businessParams"); __publicField(this, "systemConfig"); } initProperty(systemConfig2, businessParams) { this.businessParams = { ...businessParams }; this.systemConfig = { ...systemConfig2 }; } onActive() { this.webSocketInstance = wx.connectSocket({ url: this.generateRequestUrl(), complete: () => { } }); this.webSocketInstance.onMessage((response) => { if (Object.prototype.toString.call(response.data) === "[object ArrayBuffer]") { this.forwardToHandler(new Uint8Array(response.data)); } else { const data = JSON.parse(response.data); if (data.event === "sentence_end") { this.taskCompletedCallback(); } } }); this.webSocketInstance.onOpen(() => { this.taskCompletedCallback(); }); } executePreCheck() { var _a; return ((_a = this.webSocketInstance) == null ? void 0 : _a.readyState) === 1; } execute(context) { if (context.isLastExecute) { this.taskCompletedCallback(); return; } if (this.webSocketInstance) { this.webSocketInstance.send({ data: JSON.stringify(this.generateRequestParams(context.taskItem.original)) }); } } generateRequestParams(text) { return { event: "text", text }; } generateRequestUrl() { return `${this.systemConfig.ttsRequestBaseUrl}?${objectToQueryString({ ...this.businessParams })}`; } onFinish() { var _a, _b; (_a = this.executeController) == null ? void 0 : _a.$bus.emit("_ttsRequestFinish"); (_b = this.webSocketInstance) == null ? void 0 : _b.close({}); this.webSocketInstance = null; } } var SystemStatus = /* @__PURE__ */ ((SystemStatus2) => { SystemStatus2["OFFLINE"] = "offline"; SystemStatus2["EXECUTE"] = "execute"; return SystemStatus2; })(SystemStatus || {}); class TtsController { constructor(systemConfig2, businessParams, ssmlConfig) { __publicField(this, "textSplitInstance", new TextSplit()); __publicField(this, "createSsmlInstance"); __publicField(this, "audioActuatorInstance", new AudioActuator()); __publicField(this, "ttsRequestInstance", new TtsRequest()); __publicField(this, "byteBufferInstance", new ByteBuffer()); __publicField(this, "decodeDataInstance", new DecodeData()); __publicField(this, "systemStatus", SystemStatus.OFFLINE); __publicField(this, "$bus", createEventBus()); this.ttsRequestInstance.initProperty(systemConfig2, businessParams); this.createSsmlInstance = new CreateSsml(); this.createSsmlInstance.initProperty(ssmlConfig); this.textSplitInstance.setExecuteController(this); this.createSsmlInstance.setExecuteController(this); this.ttsRequestInstance.setExecuteController(this); this.byteBufferInstance.setExecuteController(this); this.decodeDataInstance.setExecuteController(this); this.audioActuatorInstance.setExecuteController(this); this.textSplitInstance.linkHandler(this.createSsmlInstance); this.createSsmlInstance.linkHandler(this.ttsRequestInstance); this.ttsRequestInstance.linkHandler(this.byteBufferInstance); this.byteBufferInstance.linkHandler(this.decodeDataInstance); this.decodeDataInstance.linkHandler(this.audioActuatorInstance); if (wx.setInnerAudioOption) { wx.setInnerAudioOption({ obeyMuteSwitch: false }); } this.bindEvent(); } /** * 绑定事件监听器 */ bindEvent() { this.$bus.on("_audioActuatorBeforeFirstExecute", () => { this.emit("audioFirstStart"); }); this.$bus.on("_appError", (error) => { this.finish(); this.emit("appError", error); }); this.$bus.on("_appFinish", () => { this.finish(); }); } /** * 进入待机状态,等待传入文本数据 */ start() { if (this.systemStatus === SystemStatus.EXECUTE) { return this; } this.textSplitInstance.triggerHandlerActive(); this.createSsmlInstance.triggerHandlerActive(); this.ttsRequestInstance.triggerHandlerActive(); this.byteBufferInstance.triggerHandlerActive(); this.decodeDataInstance.triggerHandlerActive(); this.audioActuatorInstance.triggerHandlerActive(); this.systemStatus = SystemStatus.EXECUTE; return this; } /** * 传入文本数据 * * @param text 待转换的文本 */ send(text) { if (this.systemStatus !== SystemStatus.EXECUTE) { return this; } this.textSplitInstance.handle(text); return this; } /** * 应用停止处理传入的文本,但是并不会停止音频播放 */ end() { if (this.systemStatus !== SystemStatus.EXECUTE) { return this; } this.textSplitInstance.handle(null); this.textSplitInstance.isHandleDataAcceptedComplete = true; return this; } /** * 停止所有处理器,并且重置状态,触发 appFinish 事件 */ finish() { if (this.systemStatus === SystemStatus.OFFLINE) { return; } this.textSplitInstance.triggerHandlerFinish(); this.createSsmlInstance.triggerHandlerFinish(); this.ttsRequestInstance.triggerHandlerFinish(); this.byteBufferInstance.triggerHandlerFinish(); this.decodeDataInstance.triggerHandlerFinish(); this.audioActuatorInstance.triggerHandlerFinish(); this.systemStatus = SystemStatus.OFFLINE; this.emit("appFinish"); } emit(eventName, data) { this.$bus.emit(eventName, data); } on(eventName, callback) { this.$bus.on(eventName, callback); return this; } } let systemConfig; const defaultSystemConfig = { ttsRequestBaseUrl: "wss://audio.workbrain.cn/tts" }; const defaultBusinessParams = { voice_type: "zh_female_daimengchuanmei_moon_bigtts", text_type: "plain", speed_ratio: 1, volume_ratio: 1, pitch_ratio: 1, language: "cn", provider: 0, cache: false, stream: true }; const defaultSsmlConfig = { pronunciationRules: [] }; const index = { /** * 配置系统参数。 * @param {SystemConfig} [_systemConfig] - 系统配置参数。 * @returns {TtsController} 返回TtsController实例。 */ config(_systemConfig) { systemConfig = { ...defaultSystemConfig, ..._systemConfig }; return this; }, /** * 创建文本转语音控制器实例。 * @param {Partial<BusinessParams>} [_businessParams] - 业务参数,默认为空对象。 * @param {Partial<SsmlConfig>} [_ssmlConfig] - SSML配置参数,默认为空对象。 * @returns {TtsController} 返回TtsController实例。 */ create(_businessParams = {}, _ssmlConfig = {}) { const businessParams = { ...defaultBusinessParams, ..._businessParams }; const ssmlConfig = { ...defaultSsmlConfig, ..._ssmlConfig }; return new TtsController(systemConfig, businessParams, ssmlConfig); } }; export { index as default };