@amaster.ai/asr-client 1.1.0-beta.49 → 1.1.0-beta.50

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/README.md CHANGED
@@ -11,11 +11,25 @@
11
11
  - 🧠 支持语音开始 / 结束事件
12
12
  - ✍️ 支持中间结果与最终转写结果
13
13
  - 🔌 简单的 WebSocket 生命周期管理
14
+ - 🌐 同时支持 WebSocket 实时识别和 HTTP 按压识别
14
15
 
15
16
  ---
16
17
 
17
18
  ## 安装与环境要求
18
19
 
20
+ ### 安装
21
+
22
+ ```bash
23
+ # npm
24
+ npm install @amaster.ai/asr-client
25
+
26
+ # pnpm
27
+ pnpm add @amaster.ai/asr-client
28
+
29
+ # yarn
30
+ yarn add @amaster.ai/asr-client
31
+ ```
32
+
19
33
  ### 浏览器要求
20
34
 
21
35
  - 支持 `getUserMedia`
@@ -28,10 +42,14 @@
28
42
 
29
43
  ## 快速开始
30
44
 
31
- ### 1️⃣ 创建 ASR Client
45
+ ### 方式一:WebSocket 实时 ASR(流式识别)
46
+
47
+ 适合需要实时看到识别结果的场景,如语音输入、实时字幕等。
48
+
49
+ #### 1️⃣ 创建 ASR Client
32
50
 
33
51
  ```ts
34
- import { createASRClient } from "./asr-client";
52
+ import { createASRClient } from "@amaster.ai/asr-client";
35
53
 
36
54
  const client = createASRClient({
37
55
  onReady() {
@@ -60,9 +78,7 @@ const client = createASRClient({
60
78
  });
61
79
  ```
62
80
 
63
- ---
64
-
65
- ### 2️⃣ 建立 WebSocket 连接
81
+ #### 2️⃣ 建立 WebSocket 连接
66
82
 
67
83
  ```ts
68
84
  await client.connect();
@@ -70,33 +86,27 @@ await client.connect();
70
86
 
71
87
  连接成功后会触发 `onReady` 回调。
72
88
 
73
- ---
74
-
75
- ### 3️⃣ 开始录音并实时识别
89
+ #### 3️⃣ 开始录音并实时识别
76
90
 
77
91
  ```ts
78
92
  await client.startRecording();
79
93
  ```
80
94
 
81
- * 自动请求麦克风权限
82
- * 自动开始推送音频流
83
- * 服务端会持续返回实时转写结果
95
+ - 自动请求麦克风权限
96
+ - 自动开始推送音频流
97
+ - 服务端会持续返回实时转写结果
84
98
 
85
- ---
86
-
87
- ### 4️⃣ 停止录音
99
+ #### 4️⃣ 停止录音
88
100
 
89
101
  ```ts
90
102
  client.stopRecording();
91
103
  ```
92
104
 
93
- * 停止麦克风采集
94
- * 向服务端发送 `input_audio_buffer.commit`
95
- * 触发最终转写结果
96
-
97
- ---
105
+ - 停止麦克风采集
106
+ - 向服务端发送 `input_audio_buffer.commit`
107
+ - 触发最终转写结果
98
108
 
99
- ### 5️⃣ 关闭连接
109
+ #### 5️⃣ 关闭连接
100
110
 
101
111
  ```ts
102
112
  client.close();
@@ -104,67 +114,212 @@ client.close();
104
114
 
105
115
  ---
106
116
 
117
+ ### 方式二:HTTP 按压式 ASR(一句话识别)
118
+
119
+ 适合按住说话、松开识别的场景,如语音消息、语音搜索等。
120
+
121
+ ```tsx
122
+ import { useRef, useState } from "react";
123
+ import { createASRHttpClient } from "@amaster.ai/asr-client";
124
+
125
+ function VoiceButton() {
126
+ const [recording, setRecording] = useState(false);
127
+ const [text, setText] = useState("");
128
+ const [error, setError] = useState<string | null>(null);
129
+
130
+ // 创建 HTTP ASR 客户端
131
+ const asrHttpClient = useRef(
132
+ createASRHttpClient({
133
+ onRecordingStart() {
134
+ setRecording(true);
135
+ setText("");
136
+ setError(null);
137
+ },
138
+ onRecordingStop() {
139
+ setRecording(false);
140
+ },
141
+ onResult(result) {
142
+ setText(result);
143
+ },
144
+ onError(err) {
145
+ setError(err.message);
146
+ },
147
+ })
148
+ ).current;
149
+
150
+ return (
151
+ <div>
152
+ <button
153
+ onMouseDown={() => asrHttpClient.startRecording()}
154
+ onMouseUp={() => asrHttpClient.stopRecording()}
155
+ onTouchStart={() => asrHttpClient.startRecording()}
156
+ onTouchEnd={() => asrHttpClient.stopRecording()}
157
+ style={{
158
+ padding: "12px 24px",
159
+ background: recording ? "#f87171" : "#4ade80",
160
+ }}
161
+ >
162
+ {recording ? "松开识别" : "按住说话"}
163
+ </button>
164
+
165
+ <div>
166
+ <strong>识别结果:</strong>
167
+ <div>{text || "(暂无)"}</div>
168
+ </div>
169
+
170
+ {error && <div style={{ color: "red" }}>错误:{error}</div>}
171
+ </div>
172
+ );
173
+ }
174
+ ```
175
+
176
+ ---
177
+
107
178
  ## API 说明
108
179
 
109
180
  ### `createASRClient(config): ASRClient`
110
181
 
182
+ WebSocket 实时 ASR 客户端。
183
+
111
184
  #### `ASRClientConfig`
112
185
 
113
- | 参数 | 类型 | 说明 |
114
- | --------------- | ------------------------------------------ | -------------- |
115
- | `onReady` | `() => void` | 会话创建完成 |
116
- | `onSpeechStart` | `() => void` | 检测到语音开始 |
117
- | `onSpeechEnd` | `() => void` | 检测到语音结束 |
118
- | `onTranscript` | `(text: string, isFinal: boolean) => void` | 转写回调 |
119
- | `onError` | `(error: Error) => void` | 错误回调 |
120
- | `onClose` | `() => void` | 连接关闭回调 |
186
+ | 参数 | 类型 | 说明 |
187
+ | ---------------- | ------------------------------------------ | -------------------------------------- |
188
+ | `audioFormat` | `"pcm16" \| "g711a" \| "g711u"` | 音频格式,默认 `'pcm16'` |
189
+ | `sampleRate` | `number` | 采样率,默认 `16000` |
190
+ | `getAccessToken` | `() => string \| null` | 获取访问令牌(用于 WebSocket 认证) |
191
+ | `onReady` | `() => void` | 会话创建完成 |
192
+ | `onSpeechStart` | `() => void` | 检测到语音开始 |
193
+ | `onSpeechEnd` | `() => void` | 检测到语音结束 |
194
+ | `onTranscript` | `(text: string, isFinal: boolean) => void` | 转写回调,`isFinal` 表示是否为最终结果 |
195
+ | `onError` | `(error: Error) => void` | 错误回调 |
196
+ | `onClose` | `() => void` | 连接关闭回调 |
197
+
198
+ #### `ASRClient`
199
+
200
+ ```ts
201
+ interface ASRClient {
202
+ connect(): Promise<void>; // 建立 WebSocket 连接
203
+ startRecording(): Promise<void>; // 开始录音
204
+ stopRecording(): void; // 停止录音
205
+ close(): void; // 关闭连接
206
+ }
207
+ ```
121
208
 
122
209
  ---
123
210
 
124
- ### `ASRClient`
211
+ ### `createASRHttpClient(config): ASRHttpClient`
212
+
213
+ HTTP 按压式 ASR 客户端。
214
+
215
+ #### `ASRHttpClientConfig`
216
+
217
+ | 参数 | 类型 | 说明 |
218
+ | ------------------ | ------------------------ | -------------------- |
219
+ | `getAccessToken` | `() => string \| null` | 获取访问令牌 |
220
+ | `language` | `string` | 语言,默认 `'zh'` |
221
+ | `sampleRate` | `number` | 采样率,默认 `16000` |
222
+ | `onRecordingStart` | `() => void` | 录音开始回调 |
223
+ | `onRecordingStop` | `() => void` | 录音停止回调 |
224
+ | `onResult` | `(text: string) => void` | 识别结果回调 |
225
+ | `onError` | `(error: Error) => void` | 错误回调 |
226
+
227
+ #### `ASRHttpClient`
125
228
 
126
229
  ```ts
127
- interface ASRClient {
128
- connect(): Promise<void>;
129
- startRecording(): Promise<void>;
130
- stopRecording(): void;
131
- close(): void;
230
+ interface ASRHttpClient {
231
+ startRecording(): Promise<void>; // 开始录音
232
+ stopRecording(): Promise<string>; // 停止录音并返回识别结果
233
+ recordAndRecognize(durationMs: number): Promise<string>; // 录音指定时长
234
+ recognizeFile(file: File | Blob): Promise<string>; // 识别音频文件
235
+ recognizeUrl(audioUrl: string): Promise<string>; // 识别音频 URL
132
236
  }
133
237
  ```
134
238
 
135
239
  ---
136
240
 
241
+ ## 配合统一客户端使用
242
+
243
+ 推荐与 `@amaster.ai/client` 统一客户端一起使用,自动处理认证:
244
+
245
+ ```tsx
246
+ import { createClient } from "@amaster.ai/client";
247
+
248
+ const client = createClient({
249
+ baseURL: "https://api.amaster.ai",
250
+ });
251
+
252
+ // WebSocket ASR
253
+ const asrClient = client.asr({
254
+ onTranscript(text, isFinal) {
255
+ console.log(isFinal ? "[最终]" : "[实时]", text);
256
+ },
257
+ });
258
+
259
+ await asrClient.connect();
260
+ await asrClient.startRecording();
261
+
262
+ // HTTP ASR
263
+ const asrHttpClient = client.asrHttp({
264
+ onResult(text) {
265
+ console.log("识别结果:", text);
266
+ },
267
+ });
268
+
269
+ await asrHttpClient.startRecording();
270
+ // ... 用户说话 ...
271
+ const result = await asrHttpClient.stopRecording();
272
+ ```
273
+
274
+ ---
275
+
137
276
  ## 音频参数说明
138
277
 
139
- * **采样率**:16000 Hz
140
- * **声道**:单声道
141
- * **格式**:PCM16 → Base64
142
- * **缓冲大小**:4096 frames
278
+ ### WebSocket ASR
279
+
280
+ - **采样率**:16000 Hz
281
+ - **声道**:单声道
282
+ - **格式**:PCM16 → Base64
283
+ - **缓冲大小**:4096 frames
284
+
285
+ ### HTTP ASR
286
+
287
+ - **采样率**:自动检测(通常为 16000 Hz)
288
+ - **声道**:单声道
289
+ - **格式**:WAV(内部将 PCM 转为 WAV)
143
290
 
144
291
  ---
145
292
 
146
293
  ## 常见问题
147
294
 
295
+ ### Q: WebSocket 和 HTTP ASR 有什么区别?
296
+
297
+ | 特性 | WebSocket ASR | HTTP ASR |
298
+ | -------- | ---------------------- | ------------------- |
299
+ | 实时性 | 实时流式返回 | 说完后一次性返回 |
300
+ | 适用场景 | 语音输入、实时字幕 | 语音消息、语音搜索 |
301
+ | 交互方式 | 开始 → 持续识别 → 停止 | 按住说话 → 松开识别 |
302
+ | 网络开销 | 持续连接 | 每次请求独立 |
303
+
148
304
  ### Q: 为什么必须是 16kHz?
149
305
 
150
306
  ASR 服务通常要求 16kHz PCM 输入,否则会影响识别效果或直接报错。
151
307
 
152
- ---
153
-
154
308
  ### Q: 支持移动端吗?
155
309
 
156
310
  支持,但需注意:
157
311
 
158
- * iOS Safari 需用户手势触发录音
159
- * 后台会自动暂停音频采集
312
+ - iOS Safari 需用户手势触发录音
313
+ - 后台会自动暂停音频采集
160
314
 
161
315
  ---
162
316
 
163
317
  ## 注意事项
164
318
 
165
- * WebSocket 必须在 HTTPS 页面下使用麦克风
166
- * 页面关闭前建议调用 `client.close()`
167
- * 不建议在多个 ASR Client 实例中共享麦克风
319
+ - WebSocket 必须在 HTTPS 页面下使用麦克风
320
+ - 页面关闭前建议调用 `client.close()`
321
+ - 不建议在多个 ASR Client 实例中共享麦克风
322
+ - HTTP ASR 会自动将录音转为 WAV 格式上传
168
323
 
169
324
  ---
170
325
 
package/dist/index.cjs CHANGED
@@ -20,8 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- createASRClient: () => createASRClient,
24
- createAsrClient: () => createASRClient
23
+ createASRClient: () => asr_client_default,
24
+ createASRHttpClient: () => http_asr_client_default
25
25
  });
26
26
  module.exports = __toCommonJS(index_exports);
27
27
 
@@ -71,18 +71,10 @@ var log = (message, type = "") => {
71
71
  console.log(`[${type}]`, message);
72
72
  };
73
73
  function createASRClient(config) {
74
- const {
75
- onReady,
76
- onSpeechStart,
77
- onSpeechEnd,
78
- onTranscript,
79
- onError,
80
- onClose,
81
- path = ASR_PATH,
82
- getAccessToken
83
- } = config;
74
+ const { onReady, onSpeechStart, onSpeechEnd, onTranscript, onError, onClose, getAccessToken } = config;
84
75
  let ws = null;
85
76
  let recorder = null;
77
+ const path = ASR_PATH;
86
78
  async function connect() {
87
79
  let wsUrl = path;
88
80
  if (getAccessToken) {
@@ -168,9 +160,191 @@ function createASRClient(config) {
168
160
  close
169
161
  };
170
162
  }
163
+ var asr_client_default = (authConfig) => (config) => createASRClient({ ...authConfig, ...config });
164
+
165
+ // src/http-asr-client.ts
166
+ var ASR_HTTP_PATH = "/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions";
167
+ var RECORDER_WORKLET = `
168
+ class RecorderProcessor extends AudioWorkletProcessor {
169
+ process(inputs) {
170
+ const input = inputs[0];
171
+ if (input && input[0]) {
172
+ this.port.postMessage(input[0].slice(0));
173
+ }
174
+ return true;
175
+ }
176
+ }
177
+ registerProcessor('recorder-processor', RecorderProcessor);
178
+ `;
179
+ async function createRecorder() {
180
+ let stream;
181
+ let ctx;
182
+ let node;
183
+ let source;
184
+ const chunks = [];
185
+ return {
186
+ async start() {
187
+ stream = await navigator.mediaDevices.getUserMedia({
188
+ audio: {
189
+ channelCount: 1,
190
+ echoCancellation: true,
191
+ noiseSuppression: true,
192
+ autoGainControl: true
193
+ }
194
+ });
195
+ ctx = new AudioContext();
196
+ const blob = new Blob([RECORDER_WORKLET], { type: "application/javascript" });
197
+ const url = URL.createObjectURL(blob);
198
+ await ctx.audioWorklet.addModule(url);
199
+ URL.revokeObjectURL(url);
200
+ source = ctx.createMediaStreamSource(stream);
201
+ node = new AudioWorkletNode(ctx, "recorder-processor");
202
+ node.port.onmessage = (e) => {
203
+ const input = e.data;
204
+ const pcm = new Int16Array(input.length);
205
+ for (let i = 0; i < input.length; i++) {
206
+ const s = Math.max(-1, Math.min(1, input[i] || 0));
207
+ pcm[i] = s < 0 ? s * 32768 : s * 32767;
208
+ }
209
+ chunks.push(pcm);
210
+ };
211
+ source.connect(node);
212
+ },
213
+ async stop() {
214
+ source?.disconnect();
215
+ node?.disconnect();
216
+ stream?.getTracks().forEach((t) => t.stop());
217
+ if (ctx) {
218
+ await ctx.close();
219
+ }
220
+ const total = chunks.reduce((s, c) => s + c.length, 0);
221
+ const pcm = new Int16Array(total);
222
+ let offset = 0;
223
+ for (const c of chunks) {
224
+ pcm.set(c, offset);
225
+ offset += c.length;
226
+ }
227
+ return { pcm, sampleRate: ctx?.sampleRate ?? 16e3 };
228
+ }
229
+ };
230
+ }
231
+ function pcmToWav(pcm, sampleRate) {
232
+ const buffer = new ArrayBuffer(44 + pcm.length * 2);
233
+ const view = new DataView(buffer);
234
+ const write = (o, s) => {
235
+ for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i));
236
+ };
237
+ write(0, "RIFF");
238
+ view.setUint32(4, 36 + pcm.length * 2, true);
239
+ write(8, "WAVE");
240
+ write(12, "fmt ");
241
+ view.setUint32(16, 16, true);
242
+ view.setUint16(20, 1, true);
243
+ view.setUint16(22, 1, true);
244
+ view.setUint32(24, sampleRate, true);
245
+ view.setUint32(28, sampleRate * 2, true);
246
+ view.setUint16(32, 2, true);
247
+ view.setUint16(34, 16, true);
248
+ write(36, "data");
249
+ view.setUint32(40, pcm.length * 2, true);
250
+ for (let i = 0; i < pcm.length; i++) {
251
+ view.setInt16(44 + i * 2, pcm[i] || 0, true);
252
+ }
253
+ return new Blob([buffer], { type: "audio/wav" });
254
+ }
255
+ function blobToBase64(blob) {
256
+ return new Promise((resolve, reject) => {
257
+ const reader = new FileReader();
258
+ reader.onloadend = () => {
259
+ const result = reader.result;
260
+ resolve(result.split(",")[1] || "");
261
+ };
262
+ reader.onerror = reject;
263
+ reader.readAsDataURL(blob);
264
+ });
265
+ }
266
+ async function recognizeBlob(blob, path) {
267
+ const base64 = await blobToBase64(blob);
268
+ const response = await fetch(path ?? ASR_HTTP_PATH, {
269
+ method: "POST",
270
+ headers: { "Content-Type": "application/json" },
271
+ body: JSON.stringify({
272
+ model: "qwen3-asr-flash",
273
+ messages: [
274
+ {
275
+ role: "user",
276
+ content: [
277
+ {
278
+ type: "input_audio",
279
+ input_audio: { data: `data:audio/wav;base64,${base64}` }
280
+ }
281
+ ]
282
+ }
283
+ ]
284
+ })
285
+ });
286
+ const data = await response.json();
287
+ return data.choices?.[0]?.message?.content || "";
288
+ }
289
+ async function recognizeFile(file, path) {
290
+ return recognizeBlob(file, path);
291
+ }
292
+ function createASRHttpClient(config) {
293
+ let recorder = null;
294
+ let path = ASR_HTTP_PATH;
295
+ if (config.getAccessToken) {
296
+ const token = config.getAccessToken();
297
+ if (token) {
298
+ const separator = path.includes("?") ? "&" : "?";
299
+ path = `${path}${separator}token=${encodeURIComponent(token)}`;
300
+ }
301
+ }
302
+ return {
303
+ async startRecording() {
304
+ recorder = await createRecorder();
305
+ await recorder.start();
306
+ config.onRecordingStart?.();
307
+ },
308
+ async stopRecording() {
309
+ if (!recorder) throw new Error("Not recording");
310
+ const { pcm, sampleRate } = await recorder.stop();
311
+ recorder = null;
312
+ config.onRecordingStop?.();
313
+ const text = await recognizeBlob(pcmToWav(pcm, sampleRate), path);
314
+ config.onResult?.(text);
315
+ return text;
316
+ },
317
+ async recordAndRecognize(ms) {
318
+ await this.startRecording();
319
+ await new Promise((r) => setTimeout(r, ms));
320
+ return this.stopRecording();
321
+ },
322
+ recognizeFile: (file) => {
323
+ return recognizeFile(file, path);
324
+ },
325
+ async recognizeUrl(url) {
326
+ const res = await fetch(path, {
327
+ method: "POST",
328
+ headers: { "Content-Type": "application/json" },
329
+ body: JSON.stringify({
330
+ model: "qwen3-asr-flash",
331
+ messages: [
332
+ {
333
+ role: "user",
334
+ content: [{ type: "input_audio", input_audio: { url } }]
335
+ }
336
+ ]
337
+ })
338
+ });
339
+ const data = await res.json();
340
+ return data.choices?.[0]?.message?.content || "";
341
+ }
342
+ };
343
+ }
344
+ var http_asr_client_default = (authConfig) => (config) => createASRHttpClient({ ...authConfig, ...config });
171
345
  // Annotate the CommonJS export names for ESM import in node:
172
346
  0 && (module.exports = {
173
347
  createASRClient,
174
- createAsrClient
348
+ createASRHttpClient
175
349
  });
176
350
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/asr-client.ts"],"sourcesContent":["export * from './asr-client';\nexport { createASRClient as createAsrClient } from './asr-client';\n","/**\n * ASR Realtime WebSocket Client\n */\n\nconst ASR_PATH = \"/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime\";\n\ninterface RealtimeRecorder {\n start(onAudio: (base64: string) => void): Promise<void>;\n stop(): Promise<void>;\n}\n\nasync function createRealtimeRecorder(): Promise<RealtimeRecorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let source: MediaStreamAudioSourceNode;\n let processor: ScriptProcessorNode;\n\n return {\n async start(onAudio) {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true },\n });\n\n log(\"✅ 麦克风已启动\", \"success\");\n log(\"💬 请对着麦克风说话,实时识别中...\", \"success\");\n\n ctx = new AudioContext({ sampleRate: 16000 });\n source = ctx.createMediaStreamSource(stream);\n processor = ctx.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] || 0);\n }\n\n onAudio(btoa(binary));\n };\n\n source.connect(processor);\n processor.connect(ctx.destination);\n },\n\n async stop() {\n stream?.getTracks().forEach((t) => t.stop());\n source?.disconnect();\n processor?.disconnect();\n await ctx?.close();\n },\n };\n}\n\nconst log = (message: string, type = \"\") => {\n console.log(`[${type}]`, message);\n};\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: \"pcm16\" | \"g711a\" | \"g711u\";\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when speech is detected */\n onSpeechStart?: () => void;\n /** Called when speech stops */\n onSpeechEnd?: () => void;\n /** Called on transcript result */\n onTranscript?: (text: string, isFinal: boolean) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called on close */\n onClose?: () => void;\n /** WebSocket path, default ASR_PATH */\n path?: string;\n}\n\nexport interface ASRClient {\n /** Connect to ASR service */\n connect(): Promise<void>;\n /** Start recording from microphone */\n startRecording(): Promise<void>;\n /** Stop recording */\n stopRecording(): void;\n /** Close connection */\n close(): void;\n}\n\nexport function createASRClient(config: ASRClientConfig): ASRClient {\n const {\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n onClose,\n path = ASR_PATH,\n getAccessToken,\n } = config;\n\n let ws: WebSocket | null = null;\n let recorder: RealtimeRecorder | null = null;\n\n async function connect(): Promise<void> {\n // Build WebSocket URL with optional token parameter\n let wsUrl = path;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = path.includes('?') ? '&' : '?';\n wsUrl = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n return new Promise((resolve, reject) => {\n ws!.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"input_audio_buffer.speech_started\") {\n onSpeechStart?.();\n }\n\n if (data.type === \"input_audio_buffer.speech_stopped\") {\n onSpeechEnd?.();\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.text\") {\n onTranscript?.(data.text || data.stash || data.transcript || \"\", false);\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.completed\") {\n onTranscript?.(data.text || data.transcript || \"\", true);\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"ASR error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws!.onerror = () => {\n const err = new Error(\"WebSocket error\");\n onError?.(err);\n reject(err);\n };\n\n ws!.onclose = () => {\n recorder?.stop();\n recorder = null;\n ws = null;\n onClose?.();\n };\n });\n }\n\n async function startRecording(): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n recorder = await createRealtimeRecorder();\n await recorder.start((audio) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n ws.send(\n JSON.stringify({\n type: \"input_audio_buffer.append\",\n audio,\n })\n );\n });\n }\n\n async function stopRecording() {\n try {\n await recorder?.stop();\n } catch (err) {}\n recorder = null;\n\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"input_audio_buffer.commit\" }));\n }\n }\n\n function close() {\n stopRecording();\n ws?.close();\n ws = null;\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/asr-client.ts","../src/http-asr-client.ts"],"sourcesContent":["import createASRClient, { type ASRClientConfig, type ASRClient } from \"./asr-client\";\nexport type { ASRClient, ASRClientConfig };\nexport { createASRClient };\n\nimport createASRHttpClient, { type ASRHttpClientConfig, type ASRHttpClient } from \"./http-asr-client\";\nexport type { ASRHttpClient, ASRHttpClientConfig };\nexport { createASRHttpClient };","/**\n * ASR Realtime WebSocket Client\n *\n * WebSocket-based real-time speech recognition for streaming transcription.\n * Suitable for scenarios requiring real-time results like voice input, live captions, etc.\n *\n * @example\n * ```typescript\n * const client = createASRClient({\n * onReady() {\n * console.log(\"ASR connected\");\n * },\n * onTranscript(text, isFinal) {\n * console.log(isFinal ? \"[Final]\" : \"[Interim]\", text);\n * },\n * onError(err) {\n * console.error(\"ASR error:\", err);\n * },\n * });\n *\n * await client.connect();\n * await client.startRecording();\n * // ... speak ...\n * client.stopRecording();\n * client.close();\n * ```\n */\n\nconst ASR_PATH = \"/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime\";\n\ninterface RealtimeRecorder {\n start(onAudio: (base64: string) => void): Promise<void>;\n stop(): Promise<void>;\n}\n\nasync function createRealtimeRecorder(): Promise<RealtimeRecorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let source: MediaStreamAudioSourceNode;\n let processor: ScriptProcessorNode;\n\n return {\n async start(onAudio) {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true },\n });\n\n log(\"✅ 麦克风已启动\", \"success\");\n log(\"💬 请对着麦克风说话,实时识别中...\", \"success\");\n\n ctx = new AudioContext({ sampleRate: 16000 });\n source = ctx.createMediaStreamSource(stream);\n processor = ctx.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] || 0);\n }\n\n onAudio(btoa(binary));\n };\n\n source.connect(processor);\n processor.connect(ctx.destination);\n },\n\n async stop() {\n stream?.getTracks().forEach((t) => t.stop());\n source?.disconnect();\n processor?.disconnect();\n await ctx?.close();\n },\n };\n}\n\nconst log = (message: string, type = \"\") => {\n console.log(`[${type}]`, message);\n};\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: \"pcm16\" | \"g711a\" | \"g711u\";\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when speech is detected */\n onSpeechStart?: () => void;\n /** Called when speech stops */\n onSpeechEnd?: () => void;\n /** Called on transcript result */\n onTranscript?: (text: string, isFinal: boolean) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called on close */\n onClose?: () => void;\n}\n\nexport interface ASRClient {\n /** Connect to ASR service */\n connect(): Promise<void>;\n /** Start recording from microphone */\n startRecording(): Promise<void>;\n /** Stop recording */\n stopRecording(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createASRClient(config: ASRClientConfig): ASRClient {\n const { onReady, onSpeechStart, onSpeechEnd, onTranscript, onError, onClose, getAccessToken } =\n config;\n\n let ws: WebSocket | null = null;\n let recorder: RealtimeRecorder | null = null;\n\n const path = ASR_PATH;\n\n async function connect(): Promise<void> {\n // Build WebSocket URL with optional token parameter\n let wsUrl = path;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n return new Promise((resolve, reject) => {\n ws!.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"input_audio_buffer.speech_started\") {\n onSpeechStart?.();\n }\n\n if (data.type === \"input_audio_buffer.speech_stopped\") {\n onSpeechEnd?.();\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.text\") {\n onTranscript?.(data.text || data.stash || data.transcript || \"\", false);\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.completed\") {\n onTranscript?.(data.text || data.transcript || \"\", true);\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"ASR error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws!.onerror = () => {\n const err = new Error(\"WebSocket error\");\n onError?.(err);\n reject(err);\n };\n\n ws!.onclose = () => {\n recorder?.stop();\n recorder = null;\n ws = null;\n onClose?.();\n };\n });\n }\n\n async function startRecording(): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n recorder = await createRealtimeRecorder();\n await recorder.start((audio) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n ws.send(\n JSON.stringify({\n type: \"input_audio_buffer.append\",\n audio,\n })\n );\n });\n }\n\n async function stopRecording() {\n try {\n await recorder?.stop();\n } catch (err) {}\n recorder = null;\n\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"input_audio_buffer.commit\" }));\n }\n }\n\n function close() {\n stopRecording();\n ws?.close();\n ws = null;\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n\nexport default (authConfig: Pick<ASRClientConfig, \"getAccessToken\">) => (config: ASRClientConfig) =>\n createASRClient({ ...authConfig, ...config });\n","/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n *\n * HTTP-based speech recognition suitable for press-to-talk scenarios where you hold to speak\n * and release to recognize. Good for voice messages, voice search, etc.\n *\n * @example\n * ```typescript\n * const client = createASRHttpClient({\n * onRecordingStart() {\n * console.log(\"Recording started\");\n * },\n * onRecordingStop() {\n * console.log(\"Recording stopped\");\n * },\n * onResult(text) {\n * console.log(\"Recognized:\", text);\n * },\n * onError(err) {\n * console.error(\"ASR error:\", err);\n * },\n * });\n *\n * // Hold to speak, release to recognize\n * await client.startRecording();\n * // ... speak ...\n * const result = await client.stopRecording();\n * console.log(\"Result:\", result);\n * ```\n */\n\nconst ASR_HTTP_PATH = \"/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions\";\n\n// 录音内核\nconst RECORDER_WORKLET = `\nclass RecorderProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0]) {\n this.port.postMessage(input[0].slice(0));\n }\n return true;\n }\n}\nregisterProcessor('recorder-processor', RecorderProcessor);\n`;\n\ninterface Recorder {\n start(): Promise<void>;\n stop(): Promise<{ pcm: Int16Array; sampleRate: number }>;\n}\n\n// 创建录音器\nasync function createRecorder(): Promise<Recorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let node: AudioWorkletNode;\n let source: MediaStreamAudioSourceNode;\n const chunks: Int16Array[] = [];\n\n return {\n async start() {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n ctx = new AudioContext();\n\n const blob = new Blob([RECORDER_WORKLET], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n await ctx.audioWorklet.addModule(url);\n URL.revokeObjectURL(url);\n\n source = ctx.createMediaStreamSource(stream);\n node = new AudioWorkletNode(ctx, \"recorder-processor\");\n\n node.port.onmessage = (e) => {\n const input = e.data as Float32Array;\n const pcm = new Int16Array(input.length);\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n chunks.push(pcm);\n };\n\n source.connect(node);\n },\n\n async stop() {\n source?.disconnect();\n node?.disconnect();\n stream?.getTracks().forEach((t) => t.stop());\n if (ctx) {\n await ctx.close();\n }\n\n const total = chunks.reduce((s, c) => s + c.length, 0);\n const pcm = new Int16Array(total);\n let offset = 0;\n for (const c of chunks) {\n pcm.set(c, offset);\n offset += c.length;\n }\n\n return { pcm, sampleRate: ctx?.sampleRate ?? 16000 };\n },\n };\n}\n\n/**\n * Convert PCM to WAV Blob\n * @param pcm - PCM data\n * @param sampleRate - Sample rate\n * @returns WAV Blob\n */\nfunction pcmToWav(pcm: Int16Array, sampleRate: number): Blob {\n const buffer = new ArrayBuffer(44 + pcm.length * 2);\n const view = new DataView(buffer);\n\n const write = (o: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i));\n };\n\n write(0, \"RIFF\");\n view.setUint32(4, 36 + pcm.length * 2, true);\n write(8, \"WAVE\");\n write(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n write(36, \"data\");\n view.setUint32(40, pcm.length * 2, true);\n\n for (let i = 0; i < pcm.length; i++) {\n view.setInt16(44 + i * 2, pcm[i] || 0, true);\n }\n\n return new Blob([buffer], { type: \"audio/wav\" });\n}\n\nfunction blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const result = reader.result as string;\n resolve(result.split(\",\")[1] || \"\");\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\n/**\n * Recognize from Blob\n * @param blob - Audio Blob\n * @returns Recognized text\n */\nasync function recognizeBlob(blob: Blob, path?: string): Promise<string> {\n const base64 = await blobToBase64(blob);\n const response = await fetch(path ?? ASR_HTTP_PATH, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"qwen3-asr-flash\",\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"input_audio\",\n input_audio: { data: `data:audio/wav;base64,${base64}` },\n },\n ],\n },\n ],\n }),\n });\n\n const data = await response.json();\n return data.choices?.[0]?.message?.content || \"\";\n}\n\n/** Recognize audio file */\nexport async function recognizeFile(file: File | Blob, path?: string): Promise<string> {\n return recognizeBlob(file, path);\n}\n\nexport interface ASRHttpClientConfig {\n /** Get access token */\n getAccessToken?(): string | null;\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n let recorder: Recorder | null = null;\n\n let path = ASR_HTTP_PATH;\n\n if (config.getAccessToken) {\n const token = config.getAccessToken();\n if (token) {\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n path = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n return {\n async startRecording() {\n recorder = await createRecorder();\n await recorder.start();\n config.onRecordingStart?.();\n },\n\n async stopRecording() {\n if (!recorder) throw new Error(\"Not recording\");\n const { pcm, sampleRate } = await recorder.stop();\n recorder = null;\n config.onRecordingStop?.();\n const text = await recognizeBlob(pcmToWav(pcm, sampleRate), path);\n config.onResult?.(text);\n return text;\n },\n\n async recordAndRecognize(ms: number) {\n await this.startRecording();\n await new Promise((r) => setTimeout(r, ms));\n return this.stopRecording();\n },\n\n recognizeFile: (file: File | Blob) => {\n return recognizeFile(file, path);\n },\n\n async recognizeUrl(url: string) {\n const res = await fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"qwen3-asr-flash\",\n messages: [\n {\n role: \"user\",\n content: [{ type: \"input_audio\", input_audio: { url } }],\n },\n ],\n }),\n });\n const data = await res.json();\n return data.choices?.[0]?.message?.content || \"\";\n },\n };\n}\n\nexport default (authConfig: Pick<ASRHttpClientConfig, \"getAccessToken\">) =>\n (config: ASRHttpClientConfig) =>\n createASRHttpClient({ ...authConfig, ...config });\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,29 @@
1
1
  /**
2
2
  * ASR Realtime WebSocket Client
3
+ *
4
+ * WebSocket-based real-time speech recognition for streaming transcription.
5
+ * Suitable for scenarios requiring real-time results like voice input, live captions, etc.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const client = createASRClient({
10
+ * onReady() {
11
+ * console.log("ASR connected");
12
+ * },
13
+ * onTranscript(text, isFinal) {
14
+ * console.log(isFinal ? "[Final]" : "[Interim]", text);
15
+ * },
16
+ * onError(err) {
17
+ * console.error("ASR error:", err);
18
+ * },
19
+ * });
20
+ *
21
+ * await client.connect();
22
+ * await client.startRecording();
23
+ * // ... speak ...
24
+ * client.stopRecording();
25
+ * client.close();
26
+ * ```
3
27
  */
4
28
  interface ASRClientConfig {
5
29
  /** Audio format, default 'pcm16' */
@@ -20,8 +44,6 @@ interface ASRClientConfig {
20
44
  onError?: (error: Error) => void;
21
45
  /** Called on close */
22
46
  onClose?: () => void;
23
- /** WebSocket path, default ASR_PATH */
24
- path?: string;
25
47
  }
26
48
  interface ASRClient {
27
49
  /** Connect to ASR service */
@@ -33,6 +55,36 @@ interface ASRClient {
33
55
  /** Close connection */
34
56
  close(): void;
35
57
  }
36
- declare function createASRClient(config: ASRClientConfig): ASRClient;
58
+ declare const _default$1: (authConfig: Pick<ASRClientConfig, "getAccessToken">) => (config: ASRClientConfig) => ASRClient;
37
59
 
38
- export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient };
60
+ interface ASRHttpClientConfig {
61
+ /** Get access token */
62
+ getAccessToken?(): string | null;
63
+ /** Language, default 'zh' */
64
+ language?: string;
65
+ /** Sample rate, default 16000 */
66
+ sampleRate?: number;
67
+ /** Called when recording starts */
68
+ onRecordingStart?: () => void;
69
+ /** Called when recording stops */
70
+ onRecordingStop?: () => void;
71
+ /** Called with recognition result */
72
+ onResult?: (text: string) => void;
73
+ /** Called on error */
74
+ onError?: (error: Error) => void;
75
+ }
76
+ interface ASRHttpClient {
77
+ /** Start recording (press-to-talk) */
78
+ startRecording(): Promise<void>;
79
+ /** Stop recording and get result */
80
+ stopRecording(): Promise<string>;
81
+ /** Record for specific duration then recognize */
82
+ recordAndRecognize(durationMs: number): Promise<string>;
83
+ /** Recognize audio file (File or Blob) */
84
+ recognizeFile(file: File | Blob): Promise<string>;
85
+ /** Recognize audio from URL */
86
+ recognizeUrl(audioUrl: string): Promise<string>;
87
+ }
88
+ declare const _default: (authConfig: Pick<ASRHttpClientConfig, "getAccessToken">) => (config: ASRHttpClientConfig) => ASRHttpClient;
89
+
90
+ export { type ASRClient, type ASRClientConfig, type ASRHttpClient, type ASRHttpClientConfig, _default$1 as createASRClient, _default as createASRHttpClient };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,29 @@
1
1
  /**
2
2
  * ASR Realtime WebSocket Client
3
+ *
4
+ * WebSocket-based real-time speech recognition for streaming transcription.
5
+ * Suitable for scenarios requiring real-time results like voice input, live captions, etc.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const client = createASRClient({
10
+ * onReady() {
11
+ * console.log("ASR connected");
12
+ * },
13
+ * onTranscript(text, isFinal) {
14
+ * console.log(isFinal ? "[Final]" : "[Interim]", text);
15
+ * },
16
+ * onError(err) {
17
+ * console.error("ASR error:", err);
18
+ * },
19
+ * });
20
+ *
21
+ * await client.connect();
22
+ * await client.startRecording();
23
+ * // ... speak ...
24
+ * client.stopRecording();
25
+ * client.close();
26
+ * ```
3
27
  */
4
28
  interface ASRClientConfig {
5
29
  /** Audio format, default 'pcm16' */
@@ -20,8 +44,6 @@ interface ASRClientConfig {
20
44
  onError?: (error: Error) => void;
21
45
  /** Called on close */
22
46
  onClose?: () => void;
23
- /** WebSocket path, default ASR_PATH */
24
- path?: string;
25
47
  }
26
48
  interface ASRClient {
27
49
  /** Connect to ASR service */
@@ -33,6 +55,36 @@ interface ASRClient {
33
55
  /** Close connection */
34
56
  close(): void;
35
57
  }
36
- declare function createASRClient(config: ASRClientConfig): ASRClient;
58
+ declare const _default$1: (authConfig: Pick<ASRClientConfig, "getAccessToken">) => (config: ASRClientConfig) => ASRClient;
37
59
 
38
- export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient };
60
+ interface ASRHttpClientConfig {
61
+ /** Get access token */
62
+ getAccessToken?(): string | null;
63
+ /** Language, default 'zh' */
64
+ language?: string;
65
+ /** Sample rate, default 16000 */
66
+ sampleRate?: number;
67
+ /** Called when recording starts */
68
+ onRecordingStart?: () => void;
69
+ /** Called when recording stops */
70
+ onRecordingStop?: () => void;
71
+ /** Called with recognition result */
72
+ onResult?: (text: string) => void;
73
+ /** Called on error */
74
+ onError?: (error: Error) => void;
75
+ }
76
+ interface ASRHttpClient {
77
+ /** Start recording (press-to-talk) */
78
+ startRecording(): Promise<void>;
79
+ /** Stop recording and get result */
80
+ stopRecording(): Promise<string>;
81
+ /** Record for specific duration then recognize */
82
+ recordAndRecognize(durationMs: number): Promise<string>;
83
+ /** Recognize audio file (File or Blob) */
84
+ recognizeFile(file: File | Blob): Promise<string>;
85
+ /** Recognize audio from URL */
86
+ recognizeUrl(audioUrl: string): Promise<string>;
87
+ }
88
+ declare const _default: (authConfig: Pick<ASRHttpClientConfig, "getAccessToken">) => (config: ASRHttpClientConfig) => ASRHttpClient;
89
+
90
+ export { type ASRClient, type ASRClientConfig, type ASRHttpClient, type ASRHttpClientConfig, _default$1 as createASRClient, _default as createASRHttpClient };
package/dist/index.js CHANGED
@@ -44,18 +44,10 @@ var log = (message, type = "") => {
44
44
  console.log(`[${type}]`, message);
45
45
  };
46
46
  function createASRClient(config) {
47
- const {
48
- onReady,
49
- onSpeechStart,
50
- onSpeechEnd,
51
- onTranscript,
52
- onError,
53
- onClose,
54
- path = ASR_PATH,
55
- getAccessToken
56
- } = config;
47
+ const { onReady, onSpeechStart, onSpeechEnd, onTranscript, onError, onClose, getAccessToken } = config;
57
48
  let ws = null;
58
49
  let recorder = null;
50
+ const path = ASR_PATH;
59
51
  async function connect() {
60
52
  let wsUrl = path;
61
53
  if (getAccessToken) {
@@ -141,8 +133,190 @@ function createASRClient(config) {
141
133
  close
142
134
  };
143
135
  }
136
+ var asr_client_default = (authConfig) => (config) => createASRClient({ ...authConfig, ...config });
137
+
138
+ // src/http-asr-client.ts
139
+ var ASR_HTTP_PATH = "/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions";
140
+ var RECORDER_WORKLET = `
141
+ class RecorderProcessor extends AudioWorkletProcessor {
142
+ process(inputs) {
143
+ const input = inputs[0];
144
+ if (input && input[0]) {
145
+ this.port.postMessage(input[0].slice(0));
146
+ }
147
+ return true;
148
+ }
149
+ }
150
+ registerProcessor('recorder-processor', RecorderProcessor);
151
+ `;
152
+ async function createRecorder() {
153
+ let stream;
154
+ let ctx;
155
+ let node;
156
+ let source;
157
+ const chunks = [];
158
+ return {
159
+ async start() {
160
+ stream = await navigator.mediaDevices.getUserMedia({
161
+ audio: {
162
+ channelCount: 1,
163
+ echoCancellation: true,
164
+ noiseSuppression: true,
165
+ autoGainControl: true
166
+ }
167
+ });
168
+ ctx = new AudioContext();
169
+ const blob = new Blob([RECORDER_WORKLET], { type: "application/javascript" });
170
+ const url = URL.createObjectURL(blob);
171
+ await ctx.audioWorklet.addModule(url);
172
+ URL.revokeObjectURL(url);
173
+ source = ctx.createMediaStreamSource(stream);
174
+ node = new AudioWorkletNode(ctx, "recorder-processor");
175
+ node.port.onmessage = (e) => {
176
+ const input = e.data;
177
+ const pcm = new Int16Array(input.length);
178
+ for (let i = 0; i < input.length; i++) {
179
+ const s = Math.max(-1, Math.min(1, input[i] || 0));
180
+ pcm[i] = s < 0 ? s * 32768 : s * 32767;
181
+ }
182
+ chunks.push(pcm);
183
+ };
184
+ source.connect(node);
185
+ },
186
+ async stop() {
187
+ source?.disconnect();
188
+ node?.disconnect();
189
+ stream?.getTracks().forEach((t) => t.stop());
190
+ if (ctx) {
191
+ await ctx.close();
192
+ }
193
+ const total = chunks.reduce((s, c) => s + c.length, 0);
194
+ const pcm = new Int16Array(total);
195
+ let offset = 0;
196
+ for (const c of chunks) {
197
+ pcm.set(c, offset);
198
+ offset += c.length;
199
+ }
200
+ return { pcm, sampleRate: ctx?.sampleRate ?? 16e3 };
201
+ }
202
+ };
203
+ }
204
+ function pcmToWav(pcm, sampleRate) {
205
+ const buffer = new ArrayBuffer(44 + pcm.length * 2);
206
+ const view = new DataView(buffer);
207
+ const write = (o, s) => {
208
+ for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i));
209
+ };
210
+ write(0, "RIFF");
211
+ view.setUint32(4, 36 + pcm.length * 2, true);
212
+ write(8, "WAVE");
213
+ write(12, "fmt ");
214
+ view.setUint32(16, 16, true);
215
+ view.setUint16(20, 1, true);
216
+ view.setUint16(22, 1, true);
217
+ view.setUint32(24, sampleRate, true);
218
+ view.setUint32(28, sampleRate * 2, true);
219
+ view.setUint16(32, 2, true);
220
+ view.setUint16(34, 16, true);
221
+ write(36, "data");
222
+ view.setUint32(40, pcm.length * 2, true);
223
+ for (let i = 0; i < pcm.length; i++) {
224
+ view.setInt16(44 + i * 2, pcm[i] || 0, true);
225
+ }
226
+ return new Blob([buffer], { type: "audio/wav" });
227
+ }
228
+ function blobToBase64(blob) {
229
+ return new Promise((resolve, reject) => {
230
+ const reader = new FileReader();
231
+ reader.onloadend = () => {
232
+ const result = reader.result;
233
+ resolve(result.split(",")[1] || "");
234
+ };
235
+ reader.onerror = reject;
236
+ reader.readAsDataURL(blob);
237
+ });
238
+ }
239
+ async function recognizeBlob(blob, path) {
240
+ const base64 = await blobToBase64(blob);
241
+ const response = await fetch(path ?? ASR_HTTP_PATH, {
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" },
244
+ body: JSON.stringify({
245
+ model: "qwen3-asr-flash",
246
+ messages: [
247
+ {
248
+ role: "user",
249
+ content: [
250
+ {
251
+ type: "input_audio",
252
+ input_audio: { data: `data:audio/wav;base64,${base64}` }
253
+ }
254
+ ]
255
+ }
256
+ ]
257
+ })
258
+ });
259
+ const data = await response.json();
260
+ return data.choices?.[0]?.message?.content || "";
261
+ }
262
+ async function recognizeFile(file, path) {
263
+ return recognizeBlob(file, path);
264
+ }
265
+ function createASRHttpClient(config) {
266
+ let recorder = null;
267
+ let path = ASR_HTTP_PATH;
268
+ if (config.getAccessToken) {
269
+ const token = config.getAccessToken();
270
+ if (token) {
271
+ const separator = path.includes("?") ? "&" : "?";
272
+ path = `${path}${separator}token=${encodeURIComponent(token)}`;
273
+ }
274
+ }
275
+ return {
276
+ async startRecording() {
277
+ recorder = await createRecorder();
278
+ await recorder.start();
279
+ config.onRecordingStart?.();
280
+ },
281
+ async stopRecording() {
282
+ if (!recorder) throw new Error("Not recording");
283
+ const { pcm, sampleRate } = await recorder.stop();
284
+ recorder = null;
285
+ config.onRecordingStop?.();
286
+ const text = await recognizeBlob(pcmToWav(pcm, sampleRate), path);
287
+ config.onResult?.(text);
288
+ return text;
289
+ },
290
+ async recordAndRecognize(ms) {
291
+ await this.startRecording();
292
+ await new Promise((r) => setTimeout(r, ms));
293
+ return this.stopRecording();
294
+ },
295
+ recognizeFile: (file) => {
296
+ return recognizeFile(file, path);
297
+ },
298
+ async recognizeUrl(url) {
299
+ const res = await fetch(path, {
300
+ method: "POST",
301
+ headers: { "Content-Type": "application/json" },
302
+ body: JSON.stringify({
303
+ model: "qwen3-asr-flash",
304
+ messages: [
305
+ {
306
+ role: "user",
307
+ content: [{ type: "input_audio", input_audio: { url } }]
308
+ }
309
+ ]
310
+ })
311
+ });
312
+ const data = await res.json();
313
+ return data.choices?.[0]?.message?.content || "";
314
+ }
315
+ };
316
+ }
317
+ var http_asr_client_default = (authConfig) => (config) => createASRHttpClient({ ...authConfig, ...config });
144
318
  export {
145
- createASRClient,
146
- createASRClient as createAsrClient
319
+ asr_client_default as createASRClient,
320
+ http_asr_client_default as createASRHttpClient
147
321
  };
148
322
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/asr-client.ts"],"sourcesContent":["/**\n * ASR Realtime WebSocket Client\n */\n\nconst ASR_PATH = \"/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime\";\n\ninterface RealtimeRecorder {\n start(onAudio: (base64: string) => void): Promise<void>;\n stop(): Promise<void>;\n}\n\nasync function createRealtimeRecorder(): Promise<RealtimeRecorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let source: MediaStreamAudioSourceNode;\n let processor: ScriptProcessorNode;\n\n return {\n async start(onAudio) {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true },\n });\n\n log(\"✅ 麦克风已启动\", \"success\");\n log(\"💬 请对着麦克风说话,实时识别中...\", \"success\");\n\n ctx = new AudioContext({ sampleRate: 16000 });\n source = ctx.createMediaStreamSource(stream);\n processor = ctx.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] || 0);\n }\n\n onAudio(btoa(binary));\n };\n\n source.connect(processor);\n processor.connect(ctx.destination);\n },\n\n async stop() {\n stream?.getTracks().forEach((t) => t.stop());\n source?.disconnect();\n processor?.disconnect();\n await ctx?.close();\n },\n };\n}\n\nconst log = (message: string, type = \"\") => {\n console.log(`[${type}]`, message);\n};\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: \"pcm16\" | \"g711a\" | \"g711u\";\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when speech is detected */\n onSpeechStart?: () => void;\n /** Called when speech stops */\n onSpeechEnd?: () => void;\n /** Called on transcript result */\n onTranscript?: (text: string, isFinal: boolean) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called on close */\n onClose?: () => void;\n /** WebSocket path, default ASR_PATH */\n path?: string;\n}\n\nexport interface ASRClient {\n /** Connect to ASR service */\n connect(): Promise<void>;\n /** Start recording from microphone */\n startRecording(): Promise<void>;\n /** Stop recording */\n stopRecording(): void;\n /** Close connection */\n close(): void;\n}\n\nexport function createASRClient(config: ASRClientConfig): ASRClient {\n const {\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n onClose,\n path = ASR_PATH,\n getAccessToken,\n } = config;\n\n let ws: WebSocket | null = null;\n let recorder: RealtimeRecorder | null = null;\n\n async function connect(): Promise<void> {\n // Build WebSocket URL with optional token parameter\n let wsUrl = path;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = path.includes('?') ? '&' : '?';\n wsUrl = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n return new Promise((resolve, reject) => {\n ws!.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"input_audio_buffer.speech_started\") {\n onSpeechStart?.();\n }\n\n if (data.type === \"input_audio_buffer.speech_stopped\") {\n onSpeechEnd?.();\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.text\") {\n onTranscript?.(data.text || data.stash || data.transcript || \"\", false);\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.completed\") {\n onTranscript?.(data.text || data.transcript || \"\", true);\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"ASR error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws!.onerror = () => {\n const err = new Error(\"WebSocket error\");\n onError?.(err);\n reject(err);\n };\n\n ws!.onclose = () => {\n recorder?.stop();\n recorder = null;\n ws = null;\n onClose?.();\n };\n });\n }\n\n async function startRecording(): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n recorder = await createRealtimeRecorder();\n await recorder.start((audio) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n ws.send(\n JSON.stringify({\n type: \"input_audio_buffer.append\",\n audio,\n })\n );\n });\n }\n\n async function stopRecording() {\n try {\n await recorder?.stop();\n } catch (err) {}\n recorder = null;\n\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"input_audio_buffer.commit\" }));\n }\n }\n\n function close() {\n stopRecording();\n ws?.close();\n ws = null;\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/asr-client.ts","../src/http-asr-client.ts"],"sourcesContent":["/**\n * ASR Realtime WebSocket Client\n *\n * WebSocket-based real-time speech recognition for streaming transcription.\n * Suitable for scenarios requiring real-time results like voice input, live captions, etc.\n *\n * @example\n * ```typescript\n * const client = createASRClient({\n * onReady() {\n * console.log(\"ASR connected\");\n * },\n * onTranscript(text, isFinal) {\n * console.log(isFinal ? \"[Final]\" : \"[Interim]\", text);\n * },\n * onError(err) {\n * console.error(\"ASR error:\", err);\n * },\n * });\n *\n * await client.connect();\n * await client.startRecording();\n * // ... speak ...\n * client.stopRecording();\n * client.close();\n * ```\n */\n\nconst ASR_PATH = \"/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime\";\n\ninterface RealtimeRecorder {\n start(onAudio: (base64: string) => void): Promise<void>;\n stop(): Promise<void>;\n}\n\nasync function createRealtimeRecorder(): Promise<RealtimeRecorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let source: MediaStreamAudioSourceNode;\n let processor: ScriptProcessorNode;\n\n return {\n async start(onAudio) {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true },\n });\n\n log(\"✅ 麦克风已启动\", \"success\");\n log(\"💬 请对着麦克风说话,实时识别中...\", \"success\");\n\n ctx = new AudioContext({ sampleRate: 16000 });\n source = ctx.createMediaStreamSource(stream);\n processor = ctx.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n const inputData = e.inputBuffer.getChannelData(0);\n const pcm = new Int16Array(inputData.length);\n for (let i = 0; i < inputData.length; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i] || 0);\n }\n\n onAudio(btoa(binary));\n };\n\n source.connect(processor);\n processor.connect(ctx.destination);\n },\n\n async stop() {\n stream?.getTracks().forEach((t) => t.stop());\n source?.disconnect();\n processor?.disconnect();\n await ctx?.close();\n },\n };\n}\n\nconst log = (message: string, type = \"\") => {\n console.log(`[${type}]`, message);\n};\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: \"pcm16\" | \"g711a\" | \"g711u\";\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Get access token for WebSocket authentication */\n getAccessToken?: () => string | null;\n /** Called when connection is ready */\n onReady?: () => void;\n /** Called when speech is detected */\n onSpeechStart?: () => void;\n /** Called when speech stops */\n onSpeechEnd?: () => void;\n /** Called on transcript result */\n onTranscript?: (text: string, isFinal: boolean) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n /** Called on close */\n onClose?: () => void;\n}\n\nexport interface ASRClient {\n /** Connect to ASR service */\n connect(): Promise<void>;\n /** Start recording from microphone */\n startRecording(): Promise<void>;\n /** Stop recording */\n stopRecording(): void;\n /** Close connection */\n close(): void;\n}\n\nfunction createASRClient(config: ASRClientConfig): ASRClient {\n const { onReady, onSpeechStart, onSpeechEnd, onTranscript, onError, onClose, getAccessToken } =\n config;\n\n let ws: WebSocket | null = null;\n let recorder: RealtimeRecorder | null = null;\n\n const path = ASR_PATH;\n\n async function connect(): Promise<void> {\n // Build WebSocket URL with optional token parameter\n let wsUrl = path;\n if (getAccessToken) {\n const token = getAccessToken();\n if (token) {\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n wsUrl = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n ws = new WebSocket(wsUrl);\n\n return new Promise((resolve, reject) => {\n ws!.onmessage = (event) => {\n const data = JSON.parse(event.data);\n\n if (data.type === \"session.created\") {\n onReady?.();\n resolve();\n }\n\n if (data.type === \"input_audio_buffer.speech_started\") {\n onSpeechStart?.();\n }\n\n if (data.type === \"input_audio_buffer.speech_stopped\") {\n onSpeechEnd?.();\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.text\") {\n onTranscript?.(data.text || data.stash || data.transcript || \"\", false);\n }\n\n if (data.type === \"conversation.item.input_audio_transcription.completed\") {\n onTranscript?.(data.text || data.transcript || \"\", true);\n }\n\n if (data.type === \"error\") {\n const err = new Error(data.error?.message || \"ASR error\");\n onError?.(err);\n reject(err);\n }\n };\n\n ws!.onerror = () => {\n const err = new Error(\"WebSocket error\");\n onError?.(err);\n reject(err);\n };\n\n ws!.onclose = () => {\n recorder?.stop();\n recorder = null;\n ws = null;\n onClose?.();\n };\n });\n }\n\n async function startRecording(): Promise<void> {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error(\"WebSocket not connected\");\n }\n\n recorder = await createRealtimeRecorder();\n await recorder.start((audio) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n ws.send(\n JSON.stringify({\n type: \"input_audio_buffer.append\",\n audio,\n })\n );\n });\n }\n\n async function stopRecording() {\n try {\n await recorder?.stop();\n } catch (err) {}\n recorder = null;\n\n if (ws && ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: \"input_audio_buffer.commit\" }));\n }\n }\n\n function close() {\n stopRecording();\n ws?.close();\n ws = null;\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n\nexport default (authConfig: Pick<ASRClientConfig, \"getAccessToken\">) => (config: ASRClientConfig) =>\n createASRClient({ ...authConfig, ...config });\n","/**\n * HTTP ASR Client - Press-to-talk style speech recognition\n *\n * HTTP-based speech recognition suitable for press-to-talk scenarios where you hold to speak\n * and release to recognize. Good for voice messages, voice search, etc.\n *\n * @example\n * ```typescript\n * const client = createASRHttpClient({\n * onRecordingStart() {\n * console.log(\"Recording started\");\n * },\n * onRecordingStop() {\n * console.log(\"Recording stopped\");\n * },\n * onResult(text) {\n * console.log(\"Recognized:\", text);\n * },\n * onError(err) {\n * console.error(\"ASR error:\", err);\n * },\n * });\n *\n * // Hold to speak, release to recognize\n * await client.startRecording();\n * // ... speak ...\n * const result = await client.stopRecording();\n * console.log(\"Result:\", result);\n * ```\n */\n\nconst ASR_HTTP_PATH = \"/api/proxy/builtin/platform/qwen-asr/compatible-mode/v1/chat/completions\";\n\n// 录音内核\nconst RECORDER_WORKLET = `\nclass RecorderProcessor extends AudioWorkletProcessor {\n process(inputs) {\n const input = inputs[0];\n if (input && input[0]) {\n this.port.postMessage(input[0].slice(0));\n }\n return true;\n }\n}\nregisterProcessor('recorder-processor', RecorderProcessor);\n`;\n\ninterface Recorder {\n start(): Promise<void>;\n stop(): Promise<{ pcm: Int16Array; sampleRate: number }>;\n}\n\n// 创建录音器\nasync function createRecorder(): Promise<Recorder> {\n let stream: MediaStream;\n let ctx: AudioContext;\n let node: AudioWorkletNode;\n let source: MediaStreamAudioSourceNode;\n const chunks: Int16Array[] = [];\n\n return {\n async start() {\n stream = await navigator.mediaDevices.getUserMedia({\n audio: {\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n\n ctx = new AudioContext();\n\n const blob = new Blob([RECORDER_WORKLET], { type: \"application/javascript\" });\n const url = URL.createObjectURL(blob);\n await ctx.audioWorklet.addModule(url);\n URL.revokeObjectURL(url);\n\n source = ctx.createMediaStreamSource(stream);\n node = new AudioWorkletNode(ctx, \"recorder-processor\");\n\n node.port.onmessage = (e) => {\n const input = e.data as Float32Array;\n const pcm = new Int16Array(input.length);\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i] || 0));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n chunks.push(pcm);\n };\n\n source.connect(node);\n },\n\n async stop() {\n source?.disconnect();\n node?.disconnect();\n stream?.getTracks().forEach((t) => t.stop());\n if (ctx) {\n await ctx.close();\n }\n\n const total = chunks.reduce((s, c) => s + c.length, 0);\n const pcm = new Int16Array(total);\n let offset = 0;\n for (const c of chunks) {\n pcm.set(c, offset);\n offset += c.length;\n }\n\n return { pcm, sampleRate: ctx?.sampleRate ?? 16000 };\n },\n };\n}\n\n/**\n * Convert PCM to WAV Blob\n * @param pcm - PCM data\n * @param sampleRate - Sample rate\n * @returns WAV Blob\n */\nfunction pcmToWav(pcm: Int16Array, sampleRate: number): Blob {\n const buffer = new ArrayBuffer(44 + pcm.length * 2);\n const view = new DataView(buffer);\n\n const write = (o: number, s: string) => {\n for (let i = 0; i < s.length; i++) view.setUint8(o + i, s.charCodeAt(i));\n };\n\n write(0, \"RIFF\");\n view.setUint32(4, 36 + pcm.length * 2, true);\n write(8, \"WAVE\");\n write(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, 1, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, sampleRate * 2, true);\n view.setUint16(32, 2, true);\n view.setUint16(34, 16, true);\n write(36, \"data\");\n view.setUint32(40, pcm.length * 2, true);\n\n for (let i = 0; i < pcm.length; i++) {\n view.setInt16(44 + i * 2, pcm[i] || 0, true);\n }\n\n return new Blob([buffer], { type: \"audio/wav\" });\n}\n\nfunction blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const result = reader.result as string;\n resolve(result.split(\",\")[1] || \"\");\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n}\n\n/**\n * Recognize from Blob\n * @param blob - Audio Blob\n * @returns Recognized text\n */\nasync function recognizeBlob(blob: Blob, path?: string): Promise<string> {\n const base64 = await blobToBase64(blob);\n const response = await fetch(path ?? ASR_HTTP_PATH, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"qwen3-asr-flash\",\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"input_audio\",\n input_audio: { data: `data:audio/wav;base64,${base64}` },\n },\n ],\n },\n ],\n }),\n });\n\n const data = await response.json();\n return data.choices?.[0]?.message?.content || \"\";\n}\n\n/** Recognize audio file */\nexport async function recognizeFile(file: File | Blob, path?: string): Promise<string> {\n return recognizeBlob(file, path);\n}\n\nexport interface ASRHttpClientConfig {\n /** Get access token */\n getAccessToken?(): string | null;\n /** Language, default 'zh' */\n language?: string;\n /** Sample rate, default 16000 */\n sampleRate?: number;\n /** Called when recording starts */\n onRecordingStart?: () => void;\n /** Called when recording stops */\n onRecordingStop?: () => void;\n /** Called with recognition result */\n onResult?: (text: string) => void;\n /** Called on error */\n onError?: (error: Error) => void;\n}\n\nexport interface ASRHttpClient {\n /** Start recording (press-to-talk) */\n startRecording(): Promise<void>;\n /** Stop recording and get result */\n stopRecording(): Promise<string>;\n /** Record for specific duration then recognize */\n recordAndRecognize(durationMs: number): Promise<string>;\n /** Recognize audio file (File or Blob) */\n recognizeFile(file: File | Blob): Promise<string>;\n /** Recognize audio from URL */\n recognizeUrl(audioUrl: string): Promise<string>;\n}\n\nexport function createASRHttpClient(config: ASRHttpClientConfig): ASRHttpClient {\n let recorder: Recorder | null = null;\n\n let path = ASR_HTTP_PATH;\n\n if (config.getAccessToken) {\n const token = config.getAccessToken();\n if (token) {\n const separator = path.includes(\"?\") ? \"&\" : \"?\";\n path = `${path}${separator}token=${encodeURIComponent(token)}`;\n }\n }\n\n return {\n async startRecording() {\n recorder = await createRecorder();\n await recorder.start();\n config.onRecordingStart?.();\n },\n\n async stopRecording() {\n if (!recorder) throw new Error(\"Not recording\");\n const { pcm, sampleRate } = await recorder.stop();\n recorder = null;\n config.onRecordingStop?.();\n const text = await recognizeBlob(pcmToWav(pcm, sampleRate), path);\n config.onResult?.(text);\n return text;\n },\n\n async recordAndRecognize(ms: number) {\n await this.startRecording();\n await new Promise((r) => setTimeout(r, ms));\n return this.stopRecording();\n },\n\n recognizeFile: (file: File | Blob) => {\n return recognizeFile(file, path);\n },\n\n async recognizeUrl(url: string) {\n const res = await fetch(path, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n model: \"qwen3-asr-flash\",\n messages: [\n {\n role: \"user\",\n content: [{ type: \"input_audio\", input_audio: { url } }],\n },\n ],\n }),\n });\n const data = await res.json();\n return data.choices?.[0]?.message?.content || \"\";\n },\n };\n}\n\nexport default (authConfig: Pick<ASRHttpClientConfig, \"getAccessToken\">) =>\n (config: ASRHttpClientConfig) =>\n createASRHttpClient({ ...authConfig, ...config });\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/asr-client",
3
- "version": "1.1.0-beta.49",
3
+ "version": "1.1.0-beta.50",
4
4
  "description": "Qwen ASR Realtime WebSocket client with microphone recording",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",