@amaster.ai/tts-client 1.1.0-beta.51 → 1.1.0-beta.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +15 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,7 @@ function createTTSClient(config) {
|
|
|
42
42
|
let ws = null;
|
|
43
43
|
let audioChunks = [];
|
|
44
44
|
let audioContext = null;
|
|
45
|
+
let audioSource = null;
|
|
45
46
|
async function connect() {
|
|
46
47
|
return new Promise((resolve, reject) => {
|
|
47
48
|
let wsUrl = TTS_PATH;
|
|
@@ -160,25 +161,34 @@ function createTTSClient(config) {
|
|
|
160
161
|
source.connect(audioContext.destination);
|
|
161
162
|
source.onended = () => onAudioEnd?.();
|
|
162
163
|
source.start(0);
|
|
164
|
+
audioSource = source;
|
|
163
165
|
} catch (err) {
|
|
164
166
|
onError?.(err);
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
|
-
function
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
function stopAudio() {
|
|
170
|
+
if (audioSource) {
|
|
171
|
+
audioSource.stop();
|
|
172
|
+
audioSource = null;
|
|
171
173
|
}
|
|
172
174
|
if (audioContext) {
|
|
173
175
|
audioContext.close();
|
|
174
176
|
audioContext = null;
|
|
175
177
|
}
|
|
176
178
|
}
|
|
179
|
+
function close() {
|
|
180
|
+
if (ws) {
|
|
181
|
+
ws.close();
|
|
182
|
+
ws = null;
|
|
183
|
+
}
|
|
184
|
+
stopAudio();
|
|
185
|
+
}
|
|
177
186
|
return {
|
|
178
187
|
connect,
|
|
179
188
|
speak,
|
|
180
189
|
close,
|
|
181
|
-
play: playAudio
|
|
190
|
+
play: playAudio,
|
|
191
|
+
stop: stopAudio
|
|
182
192
|
};
|
|
183
193
|
}
|
|
184
194
|
var tts_client_default = (authConfig) => {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/tts-client.ts"],"sourcesContent":["import type { TTSClient, TTSClientConfig } from \"./tts-client\";\nimport createTTSClient from \"./tts-client\";\nexport { createTTSClient, type TTSClient, type TTSClientConfig };\n","/**\n * TTS Realtime WebSocket Client\n *\n * WebSocket-based real-time text-to-speech synthesis with multiple voice options.\n * Built-in playback only supports PCM format.\n *\n * @example\n * ```typescript\n * const client = createTTSClient({\n * voice: \"Cherry\",\n * autoPlay: true,\n * onReady() {\n * console.log(\"TTS ready\");\n * },\n * onAudioStart() {\n * console.log(\"Playing audio\");\n * },\n * onAudioEnd() {\n * console.log(\"Playback ended\");\n * },\n * });\n *\n * await client.connect();\n * await client.speak(\"Hello, this is a test.\");\n * // client.close();\n * ```\n */\n\nconst TTS_PATH = \"/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime\";\n\nexport interface TTSClientConfig {\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Voice name, default 'Cherry' */\n voice?: string;\n /** Auto play audio, default true */\n autoPlay?: boolean;\n /** Audio format, default 'pcm' */\n audioFormat?: \"pcm\" | \"mp3\" | \"wav\" | \"opus\";\n /** Sample rate, default 24000 */\n sampleRate?: number;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when audio playback starts */\n onAudioStart?: () => void;\n /** Called when audio playback ends */\n onAudioEnd?: () => void;\n /** Called on each audio chunk received */\n onAudioChunk?: (chunk: string[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface TTSClient {\n /** Connect to TTS service */\n connect(): Promise<void>;\n /** Synthesize speech from text */\n speak(text: string): Promise<void>;\n /** Play audio from chunks */\n play(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n getAccessToken,\n voice = \"Cherry\",\n autoPlay = true,\n audioFormat = \"pcm\",\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\n onAudioChunk,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let audioChunks: string[] = [];\n let audioContext: AudioContext | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Build WebSocket URL with optional token parameter\n let wsUrl = TTS_PATH;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = wsUrl.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${wsUrl}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {};\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n ws!.send(\n JSON.stringify({\n type: \"session.update\",\n session: {\n mode: \"server_commit\",\n voice,\n response_format: audioFormat,\n sample_rate: sampleRate,\n },\n })\n );\n }\n\n if (data.type === \"session.updated\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"response.audio.delta\") {\n audioChunks.push(data.delta);\n onAudioChunk?.(audioChunks);\n }\n\n if (data.type === \"response.audio.done\") {\n onAudioChunk?.(audioChunks);\n if (autoPlay && typeof window !== \"undefined\") {\n playAudio();\n }\n }\n\n if (data.type === \"response.done\") {\n ws!.send(JSON.stringify({ type: \"session.finish\" }));\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"Unknown error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n onError?.(err);\n reject(err);\n };\n\n ws.onclose = () => {\n ws = null;\n };\n });\n }\n\n async function speak(text: string): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n audioChunks = [];\n\n ws.send(\n JSON.stringify({\n type: \"input_text_buffer.append\",\n text,\n })\n );\n\n setTimeout(() => {\n ws!.send(\n JSON.stringify({\n type: \"input_text_buffer.commit\",\n })\n );\n }, 100);\n }\n\n function playAudio() {\n let chunks: string[] = audioChunks;\n if (typeof window === \"undefined\") return;\n\n try {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n\n onAudioStart?.();\n\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n\n for (const chunk of chunks) {\n const binaryString = atob(chunk);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) {\n combined.set(bytes, offset);\n offset += bytes.length;\n }\n\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n const int16 = dataView.getInt16(i * 2, true);\n channelData[i] = int16 / 32768.0;\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => onAudioEnd?.();\n source.start(0);\n } catch (err) {\n onError?.(err as Error);\n }\n }\n\n function close() {\n if (ws) {\n ws.close();\n ws = null;\n }\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\n }\n\n return {\n connect,\n speak,\n close,\n play: playAudio,\n };\n}\n\nexport default (\n authConfig: Pick<TTSClientConfig, \"getAccessToken\">\n): ((config: TTSClientConfig) => TTSClient) => {\n return (config: TTSClientConfig) => {\n return createTTSClient({ ...authConfig, ...config });\n };\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tts-client.ts"],"sourcesContent":["import type { TTSClient, TTSClientConfig } from \"./tts-client\";\nimport createTTSClient from \"./tts-client\";\nexport { createTTSClient, type TTSClient, type TTSClientConfig };\n","/**\n * TTS Realtime WebSocket Client\n *\n * WebSocket-based real-time text-to-speech synthesis with multiple voice options.\n * Built-in playback only supports PCM format.\n *\n * @example\n * ```typescript\n * const client = createTTSClient({\n * voice: \"Cherry\",\n * autoPlay: true,\n * onReady() {\n * console.log(\"TTS ready\");\n * },\n * onAudioStart() {\n * console.log(\"Playing audio\");\n * },\n * onAudioEnd() {\n * console.log(\"Playback ended\");\n * },\n * });\n *\n * await client.connect();\n * await client.speak(\"Hello, this is a test.\");\n * // client.close();\n * ```\n */\n\nconst TTS_PATH = \"/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime\";\n\nexport interface TTSClientConfig {\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Voice name, default 'Cherry' */\n voice?: string;\n /** Auto play audio, default true */\n autoPlay?: boolean;\n /** Audio format, default 'pcm' */\n audioFormat?: \"pcm\" | \"mp3\" | \"wav\" | \"opus\";\n /** Sample rate, default 24000 */\n sampleRate?: number;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when audio playback starts */\n onAudioStart?: () => void;\n /** Called when audio playback ends */\n onAudioEnd?: () => void;\n /** Called on each audio chunk received */\n onAudioChunk?: (chunk: string[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface TTSClient {\n /** Connect to TTS service */\n connect(): Promise<void>;\n /** Synthesize speech from text */\n speak(text: string): Promise<void>;\n /** Play audio from chunks */\n play(): void;\n /** Stop audio playback */\n stop(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n getAccessToken,\n voice = \"Cherry\",\n autoPlay = true,\n audioFormat = \"pcm\",\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\n onAudioChunk,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let audioChunks: string[] = [];\n let audioContext: AudioContext | null = null;\n let audioSource: AudioBufferSourceNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Build WebSocket URL with optional token parameter\n let wsUrl = TTS_PATH;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = wsUrl.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${wsUrl}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {};\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n ws!.send(\n JSON.stringify({\n type: \"session.update\",\n session: {\n mode: \"server_commit\",\n voice,\n response_format: audioFormat,\n sample_rate: sampleRate,\n },\n })\n );\n }\n\n if (data.type === \"session.updated\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"response.audio.delta\") {\n audioChunks.push(data.delta);\n onAudioChunk?.(audioChunks);\n }\n\n if (data.type === \"response.audio.done\") {\n onAudioChunk?.(audioChunks);\n if (autoPlay && typeof window !== \"undefined\") {\n playAudio();\n }\n }\n\n if (data.type === \"response.done\") {\n ws!.send(JSON.stringify({ type: \"session.finish\" }));\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"Unknown error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n onError?.(err);\n reject(err);\n };\n\n ws.onclose = () => {\n ws = null;\n };\n });\n }\n\n async function speak(text: string): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n audioChunks = [];\n\n ws.send(\n JSON.stringify({\n type: \"input_text_buffer.append\",\n text,\n })\n );\n\n setTimeout(() => {\n ws!.send(\n JSON.stringify({\n type: \"input_text_buffer.commit\",\n })\n );\n }, 100);\n }\n\n function playAudio() {\n let chunks: string[] = audioChunks;\n if (typeof window === \"undefined\") return;\n\n try {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n\n onAudioStart?.();\n\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n\n for (const chunk of chunks) {\n const binaryString = atob(chunk);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) {\n combined.set(bytes, offset);\n offset += bytes.length;\n }\n\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n const int16 = dataView.getInt16(i * 2, true);\n channelData[i] = int16 / 32768.0;\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => onAudioEnd?.();\n source.start(0);\n audioSource = source;\n } catch (err) {\n onError?.(err as Error);\n }\n }\n\n function stopAudio() {\n if (audioSource) {\n audioSource.stop();\n audioSource = null;\n }\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\n }\n\n function close() {\n if (ws) {\n ws.close();\n ws = null;\n }\n stopAudio();\n }\n\n return {\n connect,\n speak,\n close,\n play: playAudio,\n stop: stopAudio,\n };\n}\n\nexport default (\n authConfig: Pick<TTSClientConfig, \"getAccessToken\">\n): ((config: TTSClientConfig) => TTSClient) => {\n return (config: TTSClientConfig) => {\n return createTTSClient({ ...authConfig, ...config });\n };\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ function createTTSClient(config) {
|
|
|
16
16
|
let ws = null;
|
|
17
17
|
let audioChunks = [];
|
|
18
18
|
let audioContext = null;
|
|
19
|
+
let audioSource = null;
|
|
19
20
|
async function connect() {
|
|
20
21
|
return new Promise((resolve, reject) => {
|
|
21
22
|
let wsUrl = TTS_PATH;
|
|
@@ -134,25 +135,34 @@ function createTTSClient(config) {
|
|
|
134
135
|
source.connect(audioContext.destination);
|
|
135
136
|
source.onended = () => onAudioEnd?.();
|
|
136
137
|
source.start(0);
|
|
138
|
+
audioSource = source;
|
|
137
139
|
} catch (err) {
|
|
138
140
|
onError?.(err);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
|
-
function
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
function stopAudio() {
|
|
144
|
+
if (audioSource) {
|
|
145
|
+
audioSource.stop();
|
|
146
|
+
audioSource = null;
|
|
145
147
|
}
|
|
146
148
|
if (audioContext) {
|
|
147
149
|
audioContext.close();
|
|
148
150
|
audioContext = null;
|
|
149
151
|
}
|
|
150
152
|
}
|
|
153
|
+
function close() {
|
|
154
|
+
if (ws) {
|
|
155
|
+
ws.close();
|
|
156
|
+
ws = null;
|
|
157
|
+
}
|
|
158
|
+
stopAudio();
|
|
159
|
+
}
|
|
151
160
|
return {
|
|
152
161
|
connect,
|
|
153
162
|
speak,
|
|
154
163
|
close,
|
|
155
|
-
play: playAudio
|
|
164
|
+
play: playAudio,
|
|
165
|
+
stop: stopAudio
|
|
156
166
|
};
|
|
157
167
|
}
|
|
158
168
|
var tts_client_default = (authConfig) => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tts-client.ts"],"sourcesContent":["/**\n * TTS Realtime WebSocket Client\n *\n * WebSocket-based real-time text-to-speech synthesis with multiple voice options.\n * Built-in playback only supports PCM format.\n *\n * @example\n * ```typescript\n * const client = createTTSClient({\n * voice: \"Cherry\",\n * autoPlay: true,\n * onReady() {\n * console.log(\"TTS ready\");\n * },\n * onAudioStart() {\n * console.log(\"Playing audio\");\n * },\n * onAudioEnd() {\n * console.log(\"Playback ended\");\n * },\n * });\n *\n * await client.connect();\n * await client.speak(\"Hello, this is a test.\");\n * // client.close();\n * ```\n */\n\nconst TTS_PATH = \"/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime\";\n\nexport interface TTSClientConfig {\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Voice name, default 'Cherry' */\n voice?: string;\n /** Auto play audio, default true */\n autoPlay?: boolean;\n /** Audio format, default 'pcm' */\n audioFormat?: \"pcm\" | \"mp3\" | \"wav\" | \"opus\";\n /** Sample rate, default 24000 */\n sampleRate?: number;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when audio playback starts */\n onAudioStart?: () => void;\n /** Called when audio playback ends */\n onAudioEnd?: () => void;\n /** Called on each audio chunk received */\n onAudioChunk?: (chunk: string[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface TTSClient {\n /** Connect to TTS service */\n connect(): Promise<void>;\n /** Synthesize speech from text */\n speak(text: string): Promise<void>;\n /** Play audio from chunks */\n play(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n getAccessToken,\n voice = \"Cherry\",\n autoPlay = true,\n audioFormat = \"pcm\",\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\n onAudioChunk,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let audioChunks: string[] = [];\n let audioContext: AudioContext | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Build WebSocket URL with optional token parameter\n let wsUrl = TTS_PATH;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = wsUrl.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${wsUrl}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {};\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n ws!.send(\n JSON.stringify({\n type: \"session.update\",\n session: {\n mode: \"server_commit\",\n voice,\n response_format: audioFormat,\n sample_rate: sampleRate,\n },\n })\n );\n }\n\n if (data.type === \"session.updated\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"response.audio.delta\") {\n audioChunks.push(data.delta);\n onAudioChunk?.(audioChunks);\n }\n\n if (data.type === \"response.audio.done\") {\n onAudioChunk?.(audioChunks);\n if (autoPlay && typeof window !== \"undefined\") {\n playAudio();\n }\n }\n\n if (data.type === \"response.done\") {\n ws!.send(JSON.stringify({ type: \"session.finish\" }));\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"Unknown error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n onError?.(err);\n reject(err);\n };\n\n ws.onclose = () => {\n ws = null;\n };\n });\n }\n\n async function speak(text: string): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n audioChunks = [];\n\n ws.send(\n JSON.stringify({\n type: \"input_text_buffer.append\",\n text,\n })\n );\n\n setTimeout(() => {\n ws!.send(\n JSON.stringify({\n type: \"input_text_buffer.commit\",\n })\n );\n }, 100);\n }\n\n function playAudio() {\n let chunks: string[] = audioChunks;\n if (typeof window === \"undefined\") return;\n\n try {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n\n onAudioStart?.();\n\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n\n for (const chunk of chunks) {\n const binaryString = atob(chunk);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) {\n combined.set(bytes, offset);\n offset += bytes.length;\n }\n\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n const int16 = dataView.getInt16(i * 2, true);\n channelData[i] = int16 / 32768.0;\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => onAudioEnd?.();\n source.start(0);\n } catch (err) {\n onError?.(err as Error);\n }\n }\n\n function close() {\n if (ws) {\n ws.close();\n ws = null;\n }\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\n }\n\n return {\n connect,\n speak,\n close,\n play: playAudio,\n };\n}\n\nexport default (\n authConfig: Pick<TTSClientConfig, \"getAccessToken\">\n): ((config: TTSClientConfig) => TTSClient) => {\n return (config: TTSClientConfig) => {\n return createTTSClient({ ...authConfig, ...config });\n };\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tts-client.ts"],"sourcesContent":["/**\n * TTS Realtime WebSocket Client\n *\n * WebSocket-based real-time text-to-speech synthesis with multiple voice options.\n * Built-in playback only supports PCM format.\n *\n * @example\n * ```typescript\n * const client = createTTSClient({\n * voice: \"Cherry\",\n * autoPlay: true,\n * onReady() {\n * console.log(\"TTS ready\");\n * },\n * onAudioStart() {\n * console.log(\"Playing audio\");\n * },\n * onAudioEnd() {\n * console.log(\"Playback ended\");\n * },\n * });\n *\n * await client.connect();\n * await client.speak(\"Hello, this is a test.\");\n * // client.close();\n * ```\n */\n\nconst TTS_PATH = \"/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime\";\n\nexport interface TTSClientConfig {\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Voice name, default 'Cherry' */\n voice?: string;\n /** Auto play audio, default true */\n autoPlay?: boolean;\n /** Audio format, default 'pcm' */\n audioFormat?: \"pcm\" | \"mp3\" | \"wav\" | \"opus\";\n /** Sample rate, default 24000 */\n sampleRate?: number;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when audio playback starts */\n onAudioStart?: () => void;\n /** Called when audio playback ends */\n onAudioEnd?: () => void;\n /** Called on each audio chunk received */\n onAudioChunk?: (chunk: string[]) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface TTSClient {\n /** Connect to TTS service */\n connect(): Promise<void>;\n /** Synthesize speech from text */\n speak(text: string): Promise<void>;\n /** Play audio from chunks */\n play(): void;\n /** Stop audio playback */\n stop(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n getAccessToken,\n voice = \"Cherry\",\n autoPlay = true,\n audioFormat = \"pcm\",\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\n onAudioChunk,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let audioChunks: string[] = [];\n let audioContext: AudioContext | null = null;\n let audioSource: AudioBufferSourceNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Build WebSocket URL with optional token parameter\n let wsUrl = TTS_PATH;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = wsUrl.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${wsUrl}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {};\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n ws!.send(\n JSON.stringify({\n type: \"session.update\",\n session: {\n mode: \"server_commit\",\n voice,\n response_format: audioFormat,\n sample_rate: sampleRate,\n },\n })\n );\n }\n\n if (data.type === \"session.updated\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"response.audio.delta\") {\n audioChunks.push(data.delta);\n onAudioChunk?.(audioChunks);\n }\n\n if (data.type === \"response.audio.done\") {\n onAudioChunk?.(audioChunks);\n if (autoPlay && typeof window !== \"undefined\") {\n playAudio();\n }\n }\n\n if (data.type === \"response.done\") {\n ws!.send(JSON.stringify({ type: \"session.finish\" }));\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"Unknown error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws.onerror = () => {\n const err = new Error(\"WebSocket connection error\");\n onError?.(err);\n reject(err);\n };\n\n ws.onclose = () => {\n ws = null;\n };\n });\n }\n\n async function speak(text: string): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n audioChunks = [];\n\n ws.send(\n JSON.stringify({\n type: \"input_text_buffer.append\",\n text,\n })\n );\n\n setTimeout(() => {\n ws!.send(\n JSON.stringify({\n type: \"input_text_buffer.commit\",\n })\n );\n }, 100);\n }\n\n function playAudio() {\n let chunks: string[] = audioChunks;\n if (typeof window === \"undefined\") return;\n\n try {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n\n onAudioStart?.();\n\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n\n for (const chunk of chunks) {\n const binaryString = atob(chunk);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) {\n combined.set(bytes, offset);\n offset += bytes.length;\n }\n\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n const int16 = dataView.getInt16(i * 2, true);\n channelData[i] = int16 / 32768.0;\n }\n\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => onAudioEnd?.();\n source.start(0);\n audioSource = source;\n } catch (err) {\n onError?.(err as Error);\n }\n }\n\n function stopAudio() {\n if (audioSource) {\n audioSource.stop();\n audioSource = null;\n }\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\n }\n\n function close() {\n if (ws) {\n ws.close();\n ws = null;\n }\n stopAudio();\n }\n\n return {\n connect,\n speak,\n close,\n play: playAudio,\n stop: stopAudio,\n };\n}\n\nexport default (\n authConfig: Pick<TTSClientConfig, \"getAccessToken\">\n): ((config: TTSClientConfig) => TTSClient) => {\n return (config: TTSClientConfig) => {\n return createTTSClient({ ...authConfig, ...config });\n };\n};\n"],"mappings":"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","names":[]}
|