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

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,104 +21,12 @@ 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,
25
- speak: () => speak
24
+ createTtsClient: () => createTTSClient
26
25
  });
27
26
  module.exports = __toCommonJS(index_exports);
28
27
 
29
28
  // src/tts-client.ts
30
29
  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);
53
- }
54
- if (data.type === "response.done") {
55
- ws.send(JSON.stringify({ type: "session.finish" }));
56
- }
57
- if (data.type === "session.finished") {
58
- ws.close();
59
- if (!resolved) {
60
- resolved = true;
61
- resolve();
62
- }
63
- }
64
- if (data.type === "error") {
65
- ws.close();
66
- if (!resolved) {
67
- resolved = true;
68
- reject(new Error(data.error?.message || "TTS error"));
69
- }
70
- }
71
- };
72
- ws.onerror = () => {
73
- if (!resolved) {
74
- resolved = true;
75
- reject(new Error("TTS connection failed"));
76
- }
77
- };
78
- ws.onclose = () => {
79
- if (!resolved) {
80
- resolved = true;
81
- resolve();
82
- }
83
- };
84
- });
85
- }
86
- async function playPcmAudio(chunks, sampleRate) {
87
- if (typeof window === "undefined" || chunks.length === 0) return;
88
- const audioContext = new AudioContext();
89
- let totalBytes = 0;
90
- const allBytes = [];
91
- for (const chunk of chunks) {
92
- const binary = atob(chunk);
93
- const bytes = new Uint8Array(binary.length);
94
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
95
- allBytes.push(bytes);
96
- totalBytes += bytes.length;
97
- }
98
- const combined = new Uint8Array(totalBytes);
99
- let offset = 0;
100
- for (const bytes of allBytes) {
101
- combined.set(bytes, offset);
102
- offset += bytes.length;
103
- }
104
- const numSamples = combined.length / 2;
105
- const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);
106
- const channelData = audioBuffer.getChannelData(0);
107
- const dataView = new DataView(combined.buffer);
108
- for (let i = 0; i < numSamples; i++) {
109
- channelData[i] = dataView.getInt16(i * 2, true) / 32768;
110
- }
111
- return new Promise((resolve) => {
112
- const source = audioContext.createBufferSource();
113
- source.buffer = audioBuffer;
114
- source.connect(audioContext.destination);
115
- source.onended = () => {
116
- audioContext.close();
117
- resolve();
118
- };
119
- source.start(0);
120
- });
121
- }
122
30
  function createTTSClient(config) {
123
31
  const {
124
32
  voice = "Cherry",
@@ -182,7 +90,7 @@ function createTTSClient(config) {
182
90
  };
183
91
  });
184
92
  }
185
- async function speak2(text) {
93
+ async function speak(text) {
186
94
  if (!ws || ws.readyState !== WebSocket.OPEN) {
187
95
  throw new Error("WebSocket not connected");
188
96
  }
@@ -250,14 +158,13 @@ function createTTSClient(config) {
250
158
  }
251
159
  return {
252
160
  connect,
253
- speak: speak2,
161
+ speak,
254
162
  close
255
163
  };
256
164
  }
257
165
  // Annotate the CommonJS export names for ESM import in node:
258
166
  0 && (module.exports = {
259
167
  createTTSClient,
260
- createTtsClient,
261
- speak
168
+ createTtsClient
262
169
  });
263
170
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
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 // Play audio when all chunks received\n playPcmAudio(audioChunks, 24000);\n }\n\n if (data.type === 'response.done') {\n // Send session.finish after response complete\n ws.send(JSON.stringify({ type: 'session.finish' }));\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"]}
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":[]}
package/dist/index.d.cts CHANGED
@@ -1,11 +1,6 @@
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>;
9
4
  interface TTSClientConfig {
10
5
  /** Voice name, default 'Cherry' */
11
6
  voice?: string;
@@ -34,4 +29,4 @@ interface TTSClient {
34
29
  }
35
30
  declare function createTTSClient(config: TTSClientConfig): TTSClient;
36
31
 
37
- export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient, speak };
32
+ export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,6 @@
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>;
9
4
  interface TTSClientConfig {
10
5
  /** Voice name, default 'Cherry' */
11
6
  voice?: string;
@@ -34,4 +29,4 @@ interface TTSClient {
34
29
  }
35
30
  declare function createTTSClient(config: TTSClientConfig): TTSClient;
36
31
 
37
- export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient, speak };
32
+ export { type TTSClient, type TTSClientConfig, createTTSClient, createTTSClient as createTtsClient };
package/dist/index.js CHANGED
@@ -1,96 +1,5 @@
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);
25
- }
26
- if (data.type === "response.done") {
27
- ws.send(JSON.stringify({ type: "session.finish" }));
28
- }
29
- if (data.type === "session.finished") {
30
- ws.close();
31
- if (!resolved) {
32
- resolved = true;
33
- resolve();
34
- }
35
- }
36
- if (data.type === "error") {
37
- ws.close();
38
- if (!resolved) {
39
- resolved = true;
40
- reject(new Error(data.error?.message || "TTS error"));
41
- }
42
- }
43
- };
44
- ws.onerror = () => {
45
- if (!resolved) {
46
- resolved = true;
47
- reject(new Error("TTS connection failed"));
48
- }
49
- };
50
- ws.onclose = () => {
51
- if (!resolved) {
52
- resolved = true;
53
- resolve();
54
- }
55
- };
56
- });
57
- }
58
- async function playPcmAudio(chunks, sampleRate) {
59
- if (typeof window === "undefined" || chunks.length === 0) return;
60
- const audioContext = new AudioContext();
61
- let totalBytes = 0;
62
- const allBytes = [];
63
- for (const chunk of chunks) {
64
- const binary = atob(chunk);
65
- const bytes = new Uint8Array(binary.length);
66
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
67
- allBytes.push(bytes);
68
- totalBytes += bytes.length;
69
- }
70
- const combined = new Uint8Array(totalBytes);
71
- let offset = 0;
72
- for (const bytes of allBytes) {
73
- combined.set(bytes, offset);
74
- offset += bytes.length;
75
- }
76
- const numSamples = combined.length / 2;
77
- const audioBuffer = audioContext.createBuffer(1, numSamples, sampleRate);
78
- const channelData = audioBuffer.getChannelData(0);
79
- const dataView = new DataView(combined.buffer);
80
- for (let i = 0; i < numSamples; i++) {
81
- channelData[i] = dataView.getInt16(i * 2, true) / 32768;
82
- }
83
- return new Promise((resolve) => {
84
- const source = audioContext.createBufferSource();
85
- source.buffer = audioBuffer;
86
- source.connect(audioContext.destination);
87
- source.onended = () => {
88
- audioContext.close();
89
- resolve();
90
- };
91
- source.start(0);
92
- });
93
- }
94
3
  function createTTSClient(config) {
95
4
  const {
96
5
  voice = "Cherry",
@@ -154,7 +63,7 @@ function createTTSClient(config) {
154
63
  };
155
64
  });
156
65
  }
157
- async function speak2(text) {
66
+ async function speak(text) {
158
67
  if (!ws || ws.readyState !== WebSocket.OPEN) {
159
68
  throw new Error("WebSocket not connected");
160
69
  }
@@ -222,13 +131,12 @@ function createTTSClient(config) {
222
131
  }
223
132
  return {
224
133
  connect,
225
- speak: speak2,
134
+ speak,
226
135
  close
227
136
  };
228
137
  }
229
138
  export {
230
139
  createTTSClient,
231
- createTTSClient as createTtsClient,
232
- speak
140
+ createTTSClient as createTtsClient
233
141
  };
234
142
  //# 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\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 // Play audio when all chunks received\n playPcmAudio(audioChunks, 24000);\n }\n\n if (data.type === 'response.done') {\n // Send session.finish after response complete\n ws.send(JSON.stringify({ type: 'session.finish' }));\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"]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/tts-client",
3
- "version": "1.0.0-beta.32",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "Qwen TTS Realtime WebSocket client with audio playback",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",