UNPKG

@minto-ai/huoshan-mp-tts

Version:

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

512 lines (511 loc) 17.6 kB
var A = Object.defineProperty; var S = (s, e, t) => e in s ? A(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t; var n = (s, e, t) => S(s, typeof e != "symbol" ? e + "" : e, t); class T { constructor() { n(this, "listeners", /* @__PURE__ */ new Map()); } on(e, t) { const i = this.listeners.get(e); i ? i.push(t) : this.listeners.set(e, [t]); } emit(e, t) { const i = this.listeners.get(e); i && i.forEach((a) => { a(t); }); } clear() { this.listeners.clear(); } } let I = null; function d() { return I || (I = new T()), I; } function _() { const s = Date.now().toString(36), e = Math.floor(Math.random() * 2 ** 32).toString(36); return s + e; } function k(s) { return Object.entries(s).map(([e, t]) => `${encodeURIComponent(e)}=${encodeURIComponent(t)}`).join("&"); } class R { /** * 处理器被激活(首次激活触发) */ onActive() { } /** * 处理器首次执行任务之前 */ onBeforeFirstExecute() { } /** * 处理器执行任务完成 */ onCompleted() { } /** * 处理器执被销毁 */ onFinish() { } } class b extends R { constructor() { super(...arguments); n(this, "nextHandler", null); n(this, "executeController", null); n(this, "isFirstExecute", !0); n(this, "isHandleDataAcceptedComplete", !1); } setExecuteController(t) { this.executeController = t; } linkHandler(t) { this.nextHandler = t; } get isLastHandler() { return !this.nextHandler; } forwardToHandler(t) { this.nextHandler && this.nextHandler.handle(t); } setHandlerStatus(t) { this.handlerStatus = t; } equalHandlerStatus(t) { return this.handlerStatus === t; } } var l = /* @__PURE__ */ ((s) => (s.OFFLINE = "offline", s.ACTIVE = "active", s.PENDING = "pending", s.EXECUTING = "executing", s.COMPLETED = "completed", s.FINISH = "finish", s))(l || {}), H = /* @__PURE__ */ ((s) => (s.PENDING = "pending", s.EXECUTING = "executing", s.COMPLETED = "completed", s))(H || {}); const P = d(); class f extends b { constructor() { super(); /** * 待处理任务队列 */ n(this, "taskQueue", []); /** * 当前处理器状态 */ n(this, "handlerStatus", l.OFFLINE); } /** * 获取待处理任务数量 */ get taskQueueLength() { return this.taskQueue.length; } /** * 检查任务执行条件 * 子类可重写此方法,实现自定义的任务执行前置条件检查 * * @returns {boolean} - 当满足执行条件时返回 true,否则返回 false */ executePreCheck() { return !0; } /** * 触发处理器激活行为(空闲状态 => 激活状态) */ triggerHandlerActive() { this.setHandlerStatus(l.ACTIVE), this.onActive(), this.triggerHandlerPending(); } /** * 触发处理器待机行为(激活状态 => 待执行状态 执行状态 => 待执行状态) */ triggerHandlerPending() { this.setHandlerStatus(l.PENDING); } /** * 触发处理器执行行为(激活状态 => 执行状态) */ triggerHandlerExecute() { this.setHandlerStatus(l.EXECUTING); const t = this.taskQueue.shift(); Promise.resolve().then(() => { const { isFirstExecute: i } = this; i && (this.isFirstExecute = !1, this.onBeforeFirstExecute()); let a = !1; this.isHandleDataAcceptedComplete && !this.taskQueueLength && (a = !0); const r = { taskItem: t, isFirstExecute: i, isLastExecute: a }; this.execute(r); }); } /** * 触发处理器成功行为(执行状态 => 成功状态) */ triggerHandlerCompleted() { this.setHandlerStatus(l.COMPLETED), this.nextHandler && (this.nextHandler.handle(null), this.nextHandler.isHandleDataAcceptedComplete = !0), this.onCompleted(); } /** * 触发处理器销毁行为(待机状态 => 销毁状态) */ triggerHandlerFinish() { this.taskQueue = [], this.isFirstExecute = !0, this.isHandleDataAcceptedComplete = !1, this.setHandlerStatus(l.FINISH), this.onFinish(); } /** * 触发应用被销毁行为 */ triggerAppFinish() { P.emit("_appFinish"); } /** * 任务完成后的回调函数。 */ taskCompletedCallback() { this.taskQueueLength ? this.triggerHandlerExecute() : this.isHandleDataAcceptedComplete ? (this.triggerHandlerCompleted(), this.isLastHandler && this.triggerAppFinish()) : this.triggerHandlerPending(); } handle(t) { if (this.taskQueue.push({ uuid: _(), original: t, status: H.PENDING }), !this.executePreCheck()) { this.triggerHandlerPending(); return; } this.equalHandlerStatus(l.PENDING) && this.triggerHandlerExecute(); } } const x = d(), p = class p extends f { constructor() { super(...arguments); n(this, "audioContext", wx.createWebAudioContext()); n(this, "bufferSource", null); n(this, "gainNode", null); n(this, "volume", 1); } execute(t) { if (t.isLastExecute) { this.taskCompletedCallback(); return; } this.bufferSource = this.audioContext.createBufferSource(), this.bufferSource.buffer = t.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 + p.FADE_DURATION), this.gainNode.gain.setValueAtTime(1, this.audioContext.currentTime + t.taskItem.original.duration - p.FADE_DURATION), this.gainNode.gain.linearRampToValueAtTime(0, this.audioContext.currentTime + t.taskItem.original.duration), this.bufferSource.start(), this.bufferSource.onended = () => { this.taskCompletedCallback(); }; } onFinish() { this.bufferSource && (this.bufferSource.stop(), this.bufferSource = null), this.gainNode && (this.gainNode.disconnect(), this.gainNode = null), this.audioContext && (this.audioContext.suspend(), this.audioContext = wx.createWebAudioContext()), x.emit("_audioActuatorFinish"); } onBeforeFirstExecute() { x.emit("_audioActuatorBeforeFirstExecute"); } }; n(p, "FADE_DURATION", 0.018); let m = p; const F = d(), g = class g extends f { constructor() { super(...arguments); n(this, "lastRemainingByte", new Uint8Array([])); } execute(t) { if (t.isLastExecute) { const i = new Uint8Array([...this.lastRemainingByte]); i.length > g.MIN_LOAD_SIZE && this.forwardToHandler(i), this.lastRemainingByte = new Uint8Array([]), this.taskCompletedCallback(); } else { const i = new Uint8Array([...this.lastRemainingByte, ...t.taskItem.original]); i.length >= g.MIN_BYTE_SIZE ? (this.forwardToHandler(i), this.lastRemainingByte = new Uint8Array([])) : this.lastRemainingByte = i, this.taskCompletedCallback(); } } onFinish() { F.emit("_byteBufferFinish"), this.lastRemainingByte = new Uint8Array([]); } }; n(g, "MIN_BYTE_SIZE", 60 * 1024), n(g, "MIN_LOAD_SIZE", 0.1 * 1024); let N = g; const w = d(); class y extends f { constructor() { super(...arguments); n(this, "audioContext", wx.createWebAudioContext()); } execute(t) { if (t.isLastExecute) { this.taskCompletedCallback(); return; } const i = t.taskItem.original.buffer; if (!i) { this.taskCompletedCallback(); return; } this.audioContext.decodeAudioData(i, (a) => { this.forwardToHandler(a), this.taskCompletedCallback(); }, () => { this.forwardToHandler(this.audioContext.createBuffer(1, 1, 1)), this.taskCompletedCallback(); }); } onFinish() { this.audioContext && (this.audioContext.suspend(), this.audioContext = wx.createWebAudioContext()), w.emit("_decodeDataFinish"); } } var D = Object.defineProperty, G = (s, e, t) => e in s ? D(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t, h = (s, e, t) => G(s, typeof e != "symbol" ? e + "" : e, t), c = /* @__PURE__ */ ((s) => (s.SPACE_CHAR = "spaceChar", s.CHINESE_CHAR = "chineseChar", s.CHINESE_PUNCTUATION = "chinesePunctuation", s.ENGLISH_WORD = "englishWord", s.ENGLISH_PUNCTUATION = "englishPunctuation", s.UNKNOWN_CHAR = "unknownChar", s))(c || {}); class U { constructor() { h(this, "CHINESE_CHAR_REGEX", /[\u4E00-\u9FA5]/), h(this, "CHINESE_PUNCTUATION_REGEX", /[。?!,;:“”‘’()—…、·]/), h(this, "CHINESE_PARAGRAPH_END_REGEX", /[。?!;]/), h(this, "ENGLISH_WORD_START_REGEX", /[a-z]/i), h(this, "ENGLISH_WORD_CONTINUE_REGEX", /[a-z'’-]/i), h(this, "ENGLISH_PUNCTUATION_REGEX", /[.,!?;:'"()-]/), h(this, "ENGLISH_PARAGRAPH_END_REGEX", /[.!?;]/), h(this, "SPACE_CHAR_REGEX", /\s/), h(this, "MIN_PARAGRAPH_LENGTH", 10), h(this, "pendingTokens", []); } /** * 将输入文本转换为标记流。 * @param text 原始文本内容。 * @returns 标记化后的文本标记数组。 */ tokenizeText(e) { const t = []; let i = 0; for (; i < e.length; ) { const a = e[i]; if (this.CHINESE_CHAR_REGEX.test(a)) t.push({ type: c.CHINESE_CHAR, value: a }); else if (this.ENGLISH_WORD_START_REGEX.test(a)) { const r = i; let o = i + 1; for (; o < e.length && this.ENGLISH_WORD_CONTINUE_REGEX.test(e[o]); ) o++; const C = e.substring(r, o); t.push({ type: c.ENGLISH_WORD, value: C }), i += C.length - 1; } else this.CHINESE_PUNCTUATION_REGEX.test(a) ? t.push({ type: c.CHINESE_PUNCTUATION, value: a }) : this.ENGLISH_PUNCTUATION_REGEX.test(a) ? t.push({ type: c.ENGLISH_PUNCTUATION, value: a }) : this.SPACE_CHAR_REGEX.test(a) ? t.push({ type: c.SPACE_CHAR, value: a }) : t.push({ type: c.UNKNOWN_CHAR, value: a }); i++; } return t; } /** * 文本净化预处理 - 去除无效字符和格式。 * @param text 原始文本。 * @returns 净化后的文本。 */ sanitizeText(e) { return e.trim().replace(/\s+/g, " ").replace(/(\d+)\s+/g, "$1").replace(/\*+/g, "").replace(/[\u{1F600}-\u{1F64F}]/gu, ""); } /** * 判断标记是否为段落结束符号。 * @param token 文本标记。 * @returns 是否为段落结束符号。 */ isParagraphEndToken(e) { return this.CHINESE_PARAGRAPH_END_REGEX.test(e.value) || this.ENGLISH_PARAGRAPH_END_REGEX.test(e.value); } /** * 将标记数组转换回文本。 * @param tokens 文本标记数组。 * @returns 组合后的文本。 */ tokensToString(e) { return e.map((t) => t.value).join(""); } /** * 将标记流分割成多个段落。 * @param tokens 文本标记数组。 * @returns 分段后的标记数组集合。 */ splitToParagraphs(e) { const t = []; let i = []; for (const a of e) i.push(a), i.length >= this.MIN_PARAGRAPH_LENGTH && this.isParagraphEndToken(a) && (t.push(i), i = []); return i.length > 0 && t.push(i), t; } /** * 合并因小数点导致的错误分段。 * @param paragraphs 分段后的标记数组集合。 * @returns 合并后的标记数组集合。 */ mergeDecimalParagraphs(e) { return e.reduce((t, i) => { if (t.length === 0) t.push(i); else { const a = t[t.length - 1], r = this.tokensToString(a), o = this.tokensToString(i); /\d+\.$/.test(r) && /^\d+/.test(o) ? t[t.length - 1] = [...a, ...i] : t.push(i); } return t; }, []); } /** * 处理输入文本,返回分段后的文本数组。 * @param text 输入文本内容。 * @param includeRemaining 是否包含未完成的段落。 * @returns 分段后的文本数组。 */ processText(e, t = !1) { const i = this.tokenizeText(this.sanitizeText(e)), a = [...this.pendingTokens, ...i], r = this.mergeDecimalParagraphs(this.splitToParagraphs(a)); if (r.length > 0) { const o = r[r.length - 1], C = o[o.length - 1]; t ? this.pendingTokens = [] : this.isParagraphEndToken(C) ? /\d+\.$/.test(this.tokensToString(o)) ? this.pendingTokens = r.pop() : this.pendingTokens = [] : this.pendingTokens = r.pop(); } return r.map((o) => this.tokensToString(o)); } } const O = d(); class v extends f { constructor() { super(...arguments); n(this, "textStreamSlicer", new U()); } execute(t) { t.isLastExecute ? this.textStreamSlicer.processText("", !0).forEach((i) => { this.forwardToHandler(i); }) : this.textStreamSlicer.processText(t.taskItem.original).forEach((i) => { this.forwardToHandler(i); }), this.taskCompletedCallback(); } onFinish() { O.emit("_textSplitFinish"); } } const L = d(), B = { voice_type: "zh_female_daimengchuanmei_moon_bigtts", speed_ratio: 1, volume_ratio: 1, pitch_ratio: 1, language: "cn" }; class X extends f { constructor() { super(...arguments); n(this, "webSocketInstance", null); n(this, "businessParams"); } initProperty(t) { this.businessParams = { ...B, ...t }; } onActive() { this.webSocketInstance = wx.connectSocket({ url: this.generateRequestUrl(), complete: () => { } }), this.webSocketInstance.onMessage((t) => { Object.prototype.toString.call(t.data) === "[object ArrayBuffer]" ? this.forwardToHandler(new Uint8Array(t.data)) : JSON.parse(t.data).event === "sentence_end" && this.taskCompletedCallback(); }), this.webSocketInstance.onOpen(() => { this.taskCompletedCallback(); }); } executePreCheck() { var t; return ((t = this.webSocketInstance) == null ? void 0 : t.readyState) === 1; } execute(t) { if (t.isLastExecute) { this.taskCompletedCallback(); return; } this.webSocketInstance && this.webSocketInstance.send({ data: JSON.stringify(this.generateRequestParams(t.taskItem.original)) }); } generateRequestParams(t) { return { event: "text", text: t }; } generateRequestUrl() { return `wss://audio.workbrain.cn/tts?${k({ ...this.businessParams, stream: !0 })}`; } onFinish() { var t; L.emit("_ttsRequestFinish"), (t = this.webSocketInstance) == null || t.close({}), this.webSocketInstance = null; } } var u = /* @__PURE__ */ ((s) => (s.OFFLINE = "offline", s.EXECUTE = "execute", s))(u || {}); const E = d(); class $ { constructor(e = {}) { n(this, "textSplitInstance", new v()); n(this, "audioActuatorInstance", new m()); n(this, "ttsRequestInstance", new X()); n(this, "byteBufferInstance", new N()); n(this, "decodeDataInstance", new y()); n(this, "systemStatus", u.OFFLINE); this.ttsRequestInstance.initProperty(e), this.textSplitInstance.setExecuteController(this), this.ttsRequestInstance.setExecuteController(this), this.byteBufferInstance.setExecuteController(this), this.decodeDataInstance.setExecuteController(this), this.audioActuatorInstance.setExecuteController(this), this.textSplitInstance.linkHandler(this.ttsRequestInstance), this.ttsRequestInstance.linkHandler(this.byteBufferInstance), this.byteBufferInstance.linkHandler(this.decodeDataInstance), this.decodeDataInstance.linkHandler(this.audioActuatorInstance), wx.setInnerAudioOption && wx.setInnerAudioOption({ obeyMuteSwitch: !1 }), this.bindEvent(); } bindEvent() { E.on("_audioActuatorBeforeFirstExecute", () => { this.emit("audioFirstStart"); }), E.on("_appError", (e) => { this.finish(), this.emit("appError", e); }), E.on("_appFinish", () => { this.finish(); }); } /** * 进入待机状态,等待传入文本数据 */ start() { return this.systemStatus === u.EXECUTE ? this : (this.textSplitInstance.triggerHandlerActive(), this.ttsRequestInstance.triggerHandlerActive(), this.byteBufferInstance.triggerHandlerActive(), this.decodeDataInstance.triggerHandlerActive(), this.audioActuatorInstance.triggerHandlerActive(), this.systemStatus = u.EXECUTE, this); } /** * 传入文本数据 * * @param text 待转换的文本 */ send(e) { return this.systemStatus !== u.EXECUTE ? this : (this.textSplitInstance.handle(e), this); } /** * 应用停止处理传入的文本,但是并不会停止音频播放 */ end() { return this.systemStatus !== u.EXECUTE ? this : (this.textSplitInstance.handle(null), this.textSplitInstance.isHandleDataAcceptedComplete = !0, this); } /** * 停止所有处理器,并且重置状态,触发 appFinish 事件 */ finish() { this.systemStatus !== u.OFFLINE && (this.textSplitInstance.triggerHandlerFinish(), this.ttsRequestInstance.triggerHandlerFinish(), this.byteBufferInstance.triggerHandlerFinish(), this.decodeDataInstance.triggerHandlerFinish(), this.audioActuatorInstance.triggerHandlerFinish(), this.systemStatus = u.OFFLINE, this.emit("appFinish"), E.clear()); } emit(e, t) { E.emit(e, t); } on(e, t) { return E.on(e, t), this; } } const M = { /** * 配置系统参数。 * @returns {object} 返回当前对象,支持链式调用。 */ config() { return this; }, /** * 创建文本转语音控制器实例。 * @param {Partial<BusinessParams>} [businessParams] - 业务参数,默认为空对象。 * @returns {TtsController} 返回TtsController实例。 */ create(s = {}) { return new $(s); } }; export { M as default };