@amaster.ai/tts-client 1.0.0-beta.6 → 1.0.0-beta.7

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 CHANGED
@@ -21,12 +21,103 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  createTTSClient: () => createTTSClient,
24
- createTtsClient: () => createTTSClient
24
+ createTtsClient: () => createTTSClient,
25
+ speak: () => speak
25
26
  });
26
27
  module.exports = __toCommonJS(index_exports);
27
28
 
28
29
  // src/tts-client.ts
29
30
  var TTS_PATH = "/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime";
31
+ async function speak(text, voice = "Cherry") {
32
+ return new Promise((resolve, reject) => {
33
+ const ws = new WebSocket(TTS_PATH);
34
+ const audioChunks = [];
35
+ let resolved = false;
36
+ ws.onmessage = (event) => {
37
+ const data = JSON.parse(event.data);
38
+ if (data.type === "session.created") {
39
+ ws.send(JSON.stringify({
40
+ type: "session.update",
41
+ session: { mode: "server_commit", voice, response_format: "pcm", sample_rate: 24e3 }
42
+ }));
43
+ }
44
+ if (data.type === "session.updated") {
45
+ ws.send(JSON.stringify({ type: "input_text_buffer.append", text }));
46
+ ws.send(JSON.stringify({ type: "input_text_buffer.commit" }));
47
+ }
48
+ if (data.type === "response.audio.delta") {
49
+ audioChunks.push(data.delta);
50
+ }
51
+ if (data.type === "response.audio.done") {
52
+ playPcmAudio(audioChunks, 24e3).then(() => {
53
+ ws.send(JSON.stringify({ type: "session.finish" }));
54
+ });
55
+ }
56
+ if (data.type === "session.finished") {
57
+ ws.close();
58
+ if (!resolved) {
59
+ resolved = true;
60
+ resolve();
61
+ }
62
+ }
63
+ if (data.type === "error") {
64
+ ws.close();
65
+ if (!resolved) {
66
+ resolved = true;
67
+ reject(new Error(data.error?.message || "TTS error"));
68
+ }
69
+ }
70
+ };
71
+ ws.onerror = () => {
72
+ if (!resolved) {
73
+ resolved = true;
74
+ reject(new Error("TTS connection failed"));
75
+ }
76
+ };
77
+ ws.onclose = () => {
78
+ if (!resolved) {
79
+ resolved = true;
80
+ resolve();
81
+ }
82
+ };
83
+ });
84
+ }
85
+ async function playPcmAudio(chunks, sampleRate) {
86
+ if (typeof window === "undefined" || chunks.length === 0) return;
87
+ const audioContext = new AudioContext();
88
+ let totalBytes = 0;
89
+ const allBytes = [];
90
+ for (const chunk of chunks) {
91
+ const binary = atob(chunk);
92
+ const bytes = new Uint8Array(binary.length);
93
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
94
+ allBytes.push(bytes);
95
+ totalBytes += bytes.length;
96
+ }
97
+ const combined = new Uint8Array(totalBytes);
98
+ let offset = 0;
99
+ for (const bytes of allBytes) {
100
+ combined.set(bytes, offset);
101
+ offset += bytes.length;
102
+ }
103
+ const numSamples = combined.length / 2;
104
+ const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);
105
+ const channelData = audioBuffer.getChannelData(0);
106
+ const dataView = new DataView(combined.buffer);
107
+ for (let i = 0; i < numSamples; i++) {
108
+ channelData[i] = dataView.getInt16(i * 2, true) / 32768;
109
+ }
110
+ return new Promise((resolve) => {
111
+ const source = audioContext.createBufferSource();
112
+ source.buffer = audioBuffer;
113
+ source.connect(audioContext.destination);
114
+ source.onended = () => {
115
+ audioContext.close();
116
+ resolve();
117
+ };
118
+ source.start(0);
119
+ });
120
+ }
30
121
  function createTTSClient(config) {
31
122
  const {
32
123
  voice = "Cherry",
@@ -90,7 +181,7 @@ function createTTSClient(config) {
90
181
  };
91
182
  });
92
183
  }
93
- async function speak(text) {
184
+ async function speak2(text) {
94
185
  if (!ws || ws.readyState !== WebSocket.OPEN) {
95
186
  throw new Error("WebSocket not connected");
96
187
  }
@@ -158,13 +249,14 @@ function createTTSClient(config) {
158
249
  }
159
250
  return {
160
251
  connect,
161
- speak,
252
+ speak: speak2,
162
253
  close
163
254
  };
164
255
  }
165
256
  // Annotate the CommonJS export names for ESM import in node:
166
257
  0 && (module.exports = {
167
258
  createTTSClient,
168
- createTtsClient
259
+ createTtsClient,
260
+ speak
169
261
  });
170
262
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/tts-client.ts"],"sourcesContent":["export type { TTSClient, TTSClientConfig } from './tts-client';\nexport { createTTSClient } from './tts-client';\nexport { createTTSClient as createTtsClient } from './tts-client';\n","/**\n * TTS Realtime WebSocket Client\n */\n\nconst TTS_PATH = '/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime';\n\nexport interface TTSClientConfig {\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 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 /** Close connection */\n close(): void;\n}\n\nexport function createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n voice = 'Cherry',\n autoPlay = true,\n audioFormat = 'pcm',\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\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 ws = new WebSocket(TTS_PATH);\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(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 if (data.type === 'session.updated') {\n onReady?.();\n resolve();\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n if (autoPlay && typeof window !== 'undefined') {\n playAudio(audioChunks);\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(JSON.stringify({\n type: 'input_text_buffer.append',\n text,\n }));\n\n setTimeout(() => {\n ws!.send(JSON.stringify({\n type: 'input_text_buffer.commit',\n }));\n }, 100);\n }\n\n function playAudio(chunks: string[]) {\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 };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/tts-client.ts"],"sourcesContent":["export { speak } from './tts-client';\nexport type { TTSClient, TTSClientConfig } from './tts-client';\nexport { createTTSClient, createTTSClient as createTtsClient } from './tts-client';\n","/**\n * TTS Realtime WebSocket Client\n */\n\nconst TTS_PATH = '/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime';\n\n/**\n * One-line TTS: speak text and play audio\n * @example await speak('Hello world')\n */\nexport async function speak(text: string, voice: string = 'Cherry'): Promise<void> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(TTS_PATH);\n const audioChunks: string[] = [];\n let resolved = false;\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === 'session.created') {\n ws.send(JSON.stringify({\n type: 'session.update',\n session: { mode: 'server_commit', voice, response_format: 'pcm', sample_rate: 24000 },\n }));\n }\n\n if (data.type === 'session.updated') {\n ws.send(JSON.stringify({ type: 'input_text_buffer.append', text }));\n ws.send(JSON.stringify({ type: 'input_text_buffer.commit' }));\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n playPcmAudio(audioChunks, 24000).then(() => {\n ws.send(JSON.stringify({ type: 'session.finish' }));\n });\n }\n\n if (data.type === 'session.finished') {\n ws.close();\n if (!resolved) { resolved = true; resolve(); }\n }\n\n if (data.type === 'error') {\n ws.close();\n if (!resolved) { resolved = true; reject(new Error(data.error?.message || 'TTS error')); }\n }\n };\n\n ws.onerror = () => {\n if (!resolved) { resolved = true; reject(new Error('TTS connection failed')); }\n };\n\n ws.onclose = () => {\n if (!resolved) { resolved = true; resolve(); }\n };\n });\n}\n\n/** Play PCM audio data */\nasync function playPcmAudio(chunks: string[], sampleRate: number): Promise<void> {\n if (typeof window === 'undefined' || chunks.length === 0) return;\n\n const audioContext = new AudioContext();\n \n // Decode base64 chunks\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n for (const chunk of chunks) {\n const binary = atob(chunk);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n // Combine\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) { combined.set(bytes, offset); offset += bytes.length; }\n\n // Int16 PCM to Float32\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n channelData[i] = dataView.getInt16(i * 2, true) / 32768.0;\n }\n\n // Play\n return new Promise((resolve) => {\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => { audioContext.close(); resolve(); };\n source.start(0);\n });\n}\n\nexport interface TTSClientConfig {\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 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 /** Close connection */\n close(): void;\n}\n\nexport function createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n voice = 'Cherry',\n autoPlay = true,\n audioFormat = 'pcm',\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\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 ws = new WebSocket(TTS_PATH);\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(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 if (data.type === 'session.updated') {\n onReady?.();\n resolve();\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n if (autoPlay && typeof window !== 'undefined') {\n playAudio(audioChunks);\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(JSON.stringify({\n type: 'input_text_buffer.append',\n text,\n }));\n\n setTimeout(() => {\n ws!.send(JSON.stringify({\n type: 'input_text_buffer.commit',\n }));\n }, 100);\n }\n\n function playAudio(chunks: string[]) {\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 };\n}\n"],"mappings":"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","names":["speak"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * TTS Realtime WebSocket Client
3
3
  */
4
+ /**
5
+ * One-line TTS: speak text and play audio
6
+ * @example await speak('Hello world')
7
+ */
8
+ declare function speak(text: string, voice?: string): Promise<void>;
4
9
  interface TTSClientConfig {
5
10
  /** Voice name, default 'Cherry' */
6
11
  voice?: string;
@@ -29,4 +34,4 @@ interface TTSClient {
29
34
  }
30
35
  declare function createTTSClient(config: TTSClientConfig): TTSClient;
31
36
 
32
- export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient };
37
+ export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient, speak };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * TTS Realtime WebSocket Client
3
3
  */
4
+ /**
5
+ * One-line TTS: speak text and play audio
6
+ * @example await speak('Hello world')
7
+ */
8
+ declare function speak(text: string, voice?: string): Promise<void>;
4
9
  interface TTSClientConfig {
5
10
  /** Voice name, default 'Cherry' */
6
11
  voice?: string;
@@ -29,4 +34,4 @@ interface TTSClient {
29
34
  }
30
35
  declare function createTTSClient(config: TTSClientConfig): TTSClient;
31
36
 
32
- export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient };
37
+ export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient, speak };
package/dist/index.js CHANGED
@@ -1,5 +1,95 @@
1
1
  // src/tts-client.ts
2
2
  var TTS_PATH = "/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime";
3
+ async function speak(text, voice = "Cherry") {
4
+ return new Promise((resolve, reject) => {
5
+ const ws = new WebSocket(TTS_PATH);
6
+ const audioChunks = [];
7
+ let resolved = false;
8
+ ws.onmessage = (event) => {
9
+ const data = JSON.parse(event.data);
10
+ if (data.type === "session.created") {
11
+ ws.send(JSON.stringify({
12
+ type: "session.update",
13
+ session: { mode: "server_commit", voice, response_format: "pcm", sample_rate: 24e3 }
14
+ }));
15
+ }
16
+ if (data.type === "session.updated") {
17
+ ws.send(JSON.stringify({ type: "input_text_buffer.append", text }));
18
+ ws.send(JSON.stringify({ type: "input_text_buffer.commit" }));
19
+ }
20
+ if (data.type === "response.audio.delta") {
21
+ audioChunks.push(data.delta);
22
+ }
23
+ if (data.type === "response.audio.done") {
24
+ playPcmAudio(audioChunks, 24e3).then(() => {
25
+ ws.send(JSON.stringify({ type: "session.finish" }));
26
+ });
27
+ }
28
+ if (data.type === "session.finished") {
29
+ ws.close();
30
+ if (!resolved) {
31
+ resolved = true;
32
+ resolve();
33
+ }
34
+ }
35
+ if (data.type === "error") {
36
+ ws.close();
37
+ if (!resolved) {
38
+ resolved = true;
39
+ reject(new Error(data.error?.message || "TTS error"));
40
+ }
41
+ }
42
+ };
43
+ ws.onerror = () => {
44
+ if (!resolved) {
45
+ resolved = true;
46
+ reject(new Error("TTS connection failed"));
47
+ }
48
+ };
49
+ ws.onclose = () => {
50
+ if (!resolved) {
51
+ resolved = true;
52
+ resolve();
53
+ }
54
+ };
55
+ });
56
+ }
57
+ async function playPcmAudio(chunks, sampleRate) {
58
+ if (typeof window === "undefined" || chunks.length === 0) return;
59
+ const audioContext = new AudioContext();
60
+ let totalBytes = 0;
61
+ const allBytes = [];
62
+ for (const chunk of chunks) {
63
+ const binary = atob(chunk);
64
+ const bytes = new Uint8Array(binary.length);
65
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
66
+ allBytes.push(bytes);
67
+ totalBytes += bytes.length;
68
+ }
69
+ const combined = new Uint8Array(totalBytes);
70
+ let offset = 0;
71
+ for (const bytes of allBytes) {
72
+ combined.set(bytes, offset);
73
+ offset += bytes.length;
74
+ }
75
+ const numSamples = combined.length / 2;
76
+ const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);
77
+ const channelData = audioBuffer.getChannelData(0);
78
+ const dataView = new DataView(combined.buffer);
79
+ for (let i = 0; i < numSamples; i++) {
80
+ channelData[i] = dataView.getInt16(i * 2, true) / 32768;
81
+ }
82
+ return new Promise((resolve) => {
83
+ const source = audioContext.createBufferSource();
84
+ source.buffer = audioBuffer;
85
+ source.connect(audioContext.destination);
86
+ source.onended = () => {
87
+ audioContext.close();
88
+ resolve();
89
+ };
90
+ source.start(0);
91
+ });
92
+ }
3
93
  function createTTSClient(config) {
4
94
  const {
5
95
  voice = "Cherry",
@@ -63,7 +153,7 @@ function createTTSClient(config) {
63
153
  };
64
154
  });
65
155
  }
66
- async function speak(text) {
156
+ async function speak2(text) {
67
157
  if (!ws || ws.readyState !== WebSocket.OPEN) {
68
158
  throw new Error("WebSocket not connected");
69
159
  }
@@ -131,12 +221,13 @@ function createTTSClient(config) {
131
221
  }
132
222
  return {
133
223
  connect,
134
- speak,
224
+ speak: speak2,
135
225
  close
136
226
  };
137
227
  }
138
228
  export {
139
229
  createTTSClient,
140
- createTTSClient as createTtsClient
230
+ createTTSClient as createTtsClient,
231
+ speak
141
232
  };
142
233
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tts-client.ts"],"sourcesContent":["/**\n * TTS Realtime WebSocket Client\n */\n\nconst TTS_PATH = '/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime';\n\nexport interface TTSClientConfig {\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 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 /** Close connection */\n close(): void;\n}\n\nexport function createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n voice = 'Cherry',\n autoPlay = true,\n audioFormat = 'pcm',\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\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 ws = new WebSocket(TTS_PATH);\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(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 if (data.type === 'session.updated') {\n onReady?.();\n resolve();\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n if (autoPlay && typeof window !== 'undefined') {\n playAudio(audioChunks);\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(JSON.stringify({\n type: 'input_text_buffer.append',\n text,\n }));\n\n setTimeout(() => {\n ws!.send(JSON.stringify({\n type: 'input_text_buffer.commit',\n }));\n }, 100);\n }\n\n function playAudio(chunks: string[]) {\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 };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/tts-client.ts"],"sourcesContent":["/**\n * TTS Realtime WebSocket Client\n */\n\nconst TTS_PATH = '/api/proxy/builtin/platform/qwen-tts/api-ws/v1/realtime';\n\n/**\n * One-line TTS: speak text and play audio\n * @example await speak('Hello world')\n */\nexport async function speak(text: string, voice: string = 'Cherry'): Promise<void> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(TTS_PATH);\n const audioChunks: string[] = [];\n let resolved = false;\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === 'session.created') {\n ws.send(JSON.stringify({\n type: 'session.update',\n session: { mode: 'server_commit', voice, response_format: 'pcm', sample_rate: 24000 },\n }));\n }\n\n if (data.type === 'session.updated') {\n ws.send(JSON.stringify({ type: 'input_text_buffer.append', text }));\n ws.send(JSON.stringify({ type: 'input_text_buffer.commit' }));\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n playPcmAudio(audioChunks, 24000).then(() => {\n ws.send(JSON.stringify({ type: 'session.finish' }));\n });\n }\n\n if (data.type === 'session.finished') {\n ws.close();\n if (!resolved) { resolved = true; resolve(); }\n }\n\n if (data.type === 'error') {\n ws.close();\n if (!resolved) { resolved = true; reject(new Error(data.error?.message || 'TTS error')); }\n }\n };\n\n ws.onerror = () => {\n if (!resolved) { resolved = true; reject(new Error('TTS connection failed')); }\n };\n\n ws.onclose = () => {\n if (!resolved) { resolved = true; resolve(); }\n };\n });\n}\n\n/** Play PCM audio data */\nasync function playPcmAudio(chunks: string[], sampleRate: number): Promise<void> {\n if (typeof window === 'undefined' || chunks.length === 0) return;\n\n const audioContext = new AudioContext();\n \n // Decode base64 chunks\n let totalBytes = 0;\n const allBytes: Uint8Array[] = [];\n for (const chunk of chunks) {\n const binary = atob(chunk);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n allBytes.push(bytes);\n totalBytes += bytes.length;\n }\n\n // Combine\n const combined = new Uint8Array(totalBytes);\n let offset = 0;\n for (const bytes of allBytes) { combined.set(bytes, offset); offset += bytes.length; }\n\n // Int16 PCM to Float32\n const numSamples = combined.length / 2;\n const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);\n const channelData = audioBuffer.getChannelData(0);\n const dataView = new DataView(combined.buffer);\n for (let i = 0; i < numSamples; i++) {\n channelData[i] = dataView.getInt16(i * 2, true) / 32768.0;\n }\n\n // Play\n return new Promise((resolve) => {\n const source = audioContext.createBufferSource();\n source.buffer = audioBuffer;\n source.connect(audioContext.destination);\n source.onended = () => { audioContext.close(); resolve(); };\n source.start(0);\n });\n}\n\nexport interface TTSClientConfig {\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 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 /** Close connection */\n close(): void;\n}\n\nexport function createTTSClient(config: TTSClientConfig): TTSClient {\n const {\n voice = 'Cherry',\n autoPlay = true,\n audioFormat = 'pcm',\n sampleRate = 24000,\n onReady,\n onAudioStart,\n onAudioEnd,\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 ws = new WebSocket(TTS_PATH);\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(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 if (data.type === 'session.updated') {\n onReady?.();\n resolve();\n }\n\n if (data.type === 'response.audio.delta') {\n audioChunks.push(data.delta);\n }\n\n if (data.type === 'response.audio.done') {\n if (autoPlay && typeof window !== 'undefined') {\n playAudio(audioChunks);\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(JSON.stringify({\n type: 'input_text_buffer.append',\n text,\n }));\n\n setTimeout(() => {\n ws!.send(JSON.stringify({\n type: 'input_text_buffer.commit',\n }));\n }, 100);\n }\n\n function playAudio(chunks: string[]) {\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 };\n}\n"],"mappings":"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","names":["speak"]}
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
- {
2
- "name": "@amaster.ai/tts-client",
3
- "version": "1.0.0-beta.6",
4
- "description": "Qwen TTS Realtime WebSocket client with audio playback",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
14
- }
15
- },
16
- "files": [
17
- "dist",
18
- "README.md"
19
- ],
20
- "keywords": [
21
- "tts",
22
- "text-to-speech",
23
- "qwen",
24
- "realtime",
25
- "websocket",
26
- "audio",
27
- "speech-synthesis"
28
- ],
29
- "author": "Amaster Team",
30
- "license": "MIT",
31
- "publishConfig": {
32
- "access": "public",
33
- "registry": "https://registry.npmjs.org/"
34
- },
35
- "devDependencies": {
36
- "tsup": "^8.3.5",
37
- "typescript": "~5.7.2"
38
- },
39
- "scripts": {
40
- "build": "tsup",
41
- "dev": "tsup --watch",
42
- "clean": "rm -rf dist *.tsbuildinfo",
43
- "type-check": "tsc --noEmit"
44
- }
45
- }
1
+ {
2
+ "name": "@amaster.ai/tts-client",
3
+ "version": "1.0.0-beta.7",
4
+ "description": "Qwen TTS Realtime WebSocket client with audio playback",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "clean": "rm -rf dist *.tsbuildinfo",
24
+ "type-check": "tsc --noEmit"
25
+ },
26
+ "keywords": [
27
+ "tts",
28
+ "text-to-speech",
29
+ "qwen",
30
+ "realtime",
31
+ "websocket",
32
+ "audio",
33
+ "speech-synthesis"
34
+ ],
35
+ "author": "Amaster Team",
36
+ "license": "MIT",
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org/"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.3.5",
43
+ "typescript": "~5.7.2"
44
+ }
45
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Amaster Team
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.