@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 +96 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +94 -3
- package/dist/index.js.map +1 -1
- package/package.json +45 -45
- package/LICENSE +0 -21
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
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIA,IAAM,WAAW;AA8BV,SAAS,gBAAgB,QAAoC;AAClE,QAAM;AAAA,IACJ,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,cAAc;AAAA,IACd,aAAa;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,MAAI,KAAuB;AAC3B,MAAI,cAAwB,CAAC;AAC7B,MAAI,eAAoC;AAExC,iBAAe,UAAyB;AACtC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,IAAI,UAAU,QAAQ;AAE3B,SAAG,SAAS,MAAM;AAAA,MAAC;AAEnB,SAAG,YAAY,CAAC,UAAU;AACxB,cAAM,OAAO,KAAK,MAAM,MAAM,IAAI;AAElC,YAAI,KAAK,SAAS,mBAAmB;AACnC,aAAI,KAAK,KAAK,UAAU;AAAA,YACtB,MAAM;AAAA,YACN,SAAS;AAAA,cACP,MAAM;AAAA,cACN;AAAA,cACA,iBAAiB;AAAA,cACjB,aAAa;AAAA,YACf;AAAA,UACF,CAAC,CAAC;AAAA,QACJ;AAEA,YAAI,KAAK,SAAS,mBAAmB;AACnC,oBAAU;AACV,kBAAQ;AAAA,QACV;AAEA,YAAI,KAAK,SAAS,wBAAwB;AACxC,sBAAY,KAAK,KAAK,KAAK;AAAA,QAC7B;AAEA,YAAI,KAAK,SAAS,uBAAuB;AACvC,cAAI,YAAY,OAAO,WAAW,aAAa;AAC7C,sBAAU,WAAW;AAAA,UACvB;AAAA,QACF;AAEA,YAAI,KAAK,SAAS,iBAAiB;AACjC,aAAI,KAAK,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC,CAAC;AAAA,QACrD;AAEA,YAAI,KAAK,SAAS,SAAS;AACzB,gBAAM,MAAM,IAAI,MAAM,KAAK,OAAO,WAAW,eAAe;AAC5D,oBAAU,GAAG;AACb,iBAAO,GAAG;AAAA,QACZ;AAAA,MACF;AAEA,SAAG,UAAU,MAAM;AACjB,cAAM,MAAM,IAAI,MAAM,4BAA4B;AAClD,kBAAU,GAAG;AACb,eAAO,GAAG;AAAA,MACZ;AAEA,SAAG,UAAU,MAAM;AACjB,aAAK;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAEA,iBAAe,MAAM,MAA6B;AAChD,QAAI,CAAC,MAAM,GAAG,eAAe,UAAU,MAAM;AAC3C,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAEA,kBAAc,CAAC;AAEf,OAAG,KAAK,KAAK,UAAU;AAAA,MACrB,MAAM;AAAA,MACN;AAAA,IACF,CAAC,CAAC;AAEF,eAAW,MAAM;AACf,SAAI,KAAK,KAAK,UAAU;AAAA,QACtB,MAAM;AAAA,MACR,CAAC,CAAC;AAAA,IACJ,GAAG,GAAG;AAAA,EACR;AAEA,WAAS,UAAU,QAAkB;AACnC,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI;AACF,UAAI,CAAC,cAAc;AACjB,uBAAe,IAAI,aAAa;AAAA,MAClC;AAEA,qBAAe;AAEf,UAAI,aAAa;AACjB,YAAM,WAAyB,CAAC;AAEhC,iBAAW,SAAS,QAAQ;AAC1B,cAAM,eAAe,KAAK,KAAK;AAC/B,cAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;AAChD,iBAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,gBAAM,CAAC,IAAI,aAAa,WAAW,CAAC;AAAA,QACtC;AACA,iBAAS,KAAK,KAAK;AACnB,sBAAc,MAAM;AAAA,MACtB;AAEA,YAAM,WAAW,IAAI,WAAW,UAAU;AAC1C,UAAI,SAAS;AACb,iBAAW,SAAS,UAAU;AAC5B,iBAAS,IAAI,OAAO,MAAM;AAC1B,kBAAU,MAAM;AAAA,MAClB;AAEA,YAAM,aAAa,SAAS,SAAS;AACrC,YAAM,cAAc,aAAa,aAAa,GAAG,YAAY,UAAU;AACvE,YAAM,cAAc,YAAY,eAAe,CAAC;AAEhD,YAAM,WAAW,IAAI,SAAS,SAAS,MAAM;AAC7C,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,cAAM,QAAQ,SAAS,SAAS,IAAI,GAAG,IAAI;AAC3C,oBAAY,CAAC,IAAI,QAAQ;AAAA,MAC3B;AAEA,YAAM,SAAS,aAAa,mBAAmB;AAC/C,aAAO,SAAS;AAChB,aAAO,QAAQ,aAAa,WAAW;AACvC,aAAO,UAAU,MAAM,aAAa;AACpC,aAAO,MAAM,CAAC;AAAA,IAChB,SAAS,KAAK;AACZ,gBAAU,GAAY;AAAA,IACxB;AAAA,EACF;AAEA,WAAS,QAAQ;AACf,QAAI,IAAI;AACN,SAAG,MAAM;AACT,WAAK;AAAA,IACP;AACA,QAAI,cAAc;AAChB,mBAAa,MAAM;AACnB,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","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
|
|
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.
|
|
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
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
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.
|