From 3ecc9353c7d58f9c61578e1051903daac96f8517 Mon Sep 17 00:00:00 2001 From: abearxiong Date: Wed, 23 Apr 2025 15:41:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B5=81=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=9F=B3=E9=A2=91=E7=9A=84=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tts/provider/volcengine/tts-mix.ts | 50 +++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/src/tts/provider/volcengine/tts-mix.ts b/src/tts/provider/volcengine/tts-mix.ts index 58ee953..18fbb4c 100644 --- a/src/tts/provider/volcengine/tts-mix.ts +++ b/src/tts/provider/volcengine/tts-mix.ts @@ -1,7 +1,7 @@ // https://www.volcengine.com/docs/6561/1329505#%E7%A4%BA%E4%BE%8Bsamples import { WebSocket } from 'ws'; - +import { EventEmitter } from 'eventemitter3'; import fs from 'fs/promises'; import { nanoid } from 'nanoid'; const uuidv4 = nanoid; @@ -308,7 +308,7 @@ async function finishConnection(ws: WebSocket): Promise { return await sendEvent(ws, header, optional, payload); } -export async function runDemo(appId: string, token: string, speaker: string, text: string, outputPath: string): Promise { +export async function runDemo(appId: string, token: string, speaker: string, text: string, outputPath: string, emitter?: EventEmitter): Promise { return new Promise((resolve, reject) => { const wsHeader = { 'X-Api-App-Key': appId, @@ -319,13 +319,36 @@ export async function runDemo(appId: string, token: string, speaker: string, tex const url = 'wss://openspeech.bytedance.com/api/v3/tts/bidirection'; const ws = new WebSocket(url, { headers: wsHeader }); + const filename = outputPath.split('/').pop() || ''; + let isBegin = true; + const writeFileEmitter = (data: Buffer) => { + const value: TTSWriteType = { + type: 'tts-mix', + filename, + data, + }; + if (isBegin) { + value.isBegin = true; + isBegin = false; + } + emitter?.emit?.('writeFile', value); + }; + const finishEmitter = () => { + emitter?.emit?.('writeFile', { + type: 'tts-mix', + isEnd: true, + data: Buffer.from(''), + filename, + }); + }; ws.on('open', async () => { try { await startConnection(ws); - let isFirstResponse = true; let fileHandle: fs.FileHandle | null = null; let sessionId: string = ''; + let isFirstResponse = true; + ws.on('message', async (data) => { try { const res = parserResponse(data as Buffer); @@ -347,14 +370,17 @@ export async function runDemo(appId: string, token: string, speaker: string, tex } else if (!isFirstResponse) { if (res.optional.event === EVENT_TTSResponse && res.header.messageType === AUDIO_ONLY_RESPONSE && res.payload && fileHandle) { await fileHandle.write(res.payload); + writeFileEmitter(res.payload); } else if (res.optional.event === EVENT_TTSSentenceStart || res.optional.event === EVENT_TTSSentenceEnd) { // continue } else { + // 152 if (fileHandle) { await fileHandle.close(); fileHandle = null; } await finishConnection(ws); + finishEmitter(); } } } catch (err) { @@ -374,12 +400,22 @@ export async function runDemo(appId: string, token: string, speaker: string, tex }); } +type TTSWriteType = { + type: 'tts-mix'; + filename: string; + data?: Buffer; + isBegin?: boolean; + isEnd?: boolean; +}; + export class TtsMix { appId: string; token: string; + emitter: EventEmitter; constructor(appId: string, token: string) { this.appId = appId; this.token = token; + this.emitter = new EventEmitter(); } /** * 获取语音 @@ -389,6 +425,12 @@ export class TtsMix { * @returns */ async getVoiceDemo(speaker: string, text: string, outputPath: string): Promise { - return runDemo(this.appId, this.token, speaker, text, outputPath); + return runDemo(this.appId, this.token, speaker, text, outputPath, this.emitter); + } + onWriteFile(callback: (data: TTSWriteType) => void) { + this.emitter.on('writeFile', callback); + return () => { + this.emitter.off?.('writeFile', callback); + }; } }