@amaster.ai/asr-client 1.0.0-beta.3 → 1.0.0-beta.31
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 +82 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +80 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,12 +21,91 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
createASRClient: () => createASRClient,
|
|
24
|
-
createAsrClient: () => createASRClient
|
|
24
|
+
createAsrClient: () => createASRClient,
|
|
25
|
+
listen: () => listen
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
27
28
|
|
|
28
29
|
// src/asr-client.ts
|
|
29
30
|
var ASR_PATH = "/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime";
|
|
31
|
+
async function listen(onTranscript) {
|
|
32
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
33
|
+
throw new Error("Microphone not supported. Requires HTTPS and browser with MediaDevices API.");
|
|
34
|
+
}
|
|
35
|
+
const ws = new WebSocket(ASR_PATH);
|
|
36
|
+
let mediaStream = null;
|
|
37
|
+
let audioContext = null;
|
|
38
|
+
let processor = null;
|
|
39
|
+
let isRunning = true;
|
|
40
|
+
const stop = () => {
|
|
41
|
+
isRunning = false;
|
|
42
|
+
if (mediaStream) {
|
|
43
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
44
|
+
mediaStream = null;
|
|
45
|
+
}
|
|
46
|
+
if (processor) {
|
|
47
|
+
processor.disconnect();
|
|
48
|
+
processor = null;
|
|
49
|
+
}
|
|
50
|
+
if (audioContext) {
|
|
51
|
+
audioContext.close();
|
|
52
|
+
audioContext = null;
|
|
53
|
+
}
|
|
54
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
55
|
+
ws.send(JSON.stringify({ event_id: `event_${Date.now()}`, type: "session.finish" }));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
ws.onmessage = async (event) => {
|
|
60
|
+
const data = JSON.parse(event.data);
|
|
61
|
+
if (data.type === "session.created") {
|
|
62
|
+
try {
|
|
63
|
+
mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
64
|
+
audio: { sampleRate: 16e3, channelCount: 1, echoCancellation: true }
|
|
65
|
+
});
|
|
66
|
+
audioContext = new AudioContext({ sampleRate: 16e3 });
|
|
67
|
+
const source = audioContext.createMediaStreamSource(mediaStream);
|
|
68
|
+
processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
69
|
+
processor.onaudioprocess = (e) => {
|
|
70
|
+
if (!isRunning || ws.readyState !== WebSocket.OPEN) return;
|
|
71
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
72
|
+
const pcm = new Int16Array(input.length);
|
|
73
|
+
for (let i = 0; i < input.length; i++) {
|
|
74
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
75
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
76
|
+
}
|
|
77
|
+
const bytes = new Uint8Array(pcm.buffer);
|
|
78
|
+
let binary = "";
|
|
79
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
80
|
+
ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: btoa(binary) }));
|
|
81
|
+
};
|
|
82
|
+
source.connect(processor);
|
|
83
|
+
processor.connect(audioContext.destination);
|
|
84
|
+
resolve(stop);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
reject(err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (data.type === "conversation.item.input_audio_transcription.text") {
|
|
90
|
+
onTranscript(data.text || data.stash || "", false);
|
|
91
|
+
}
|
|
92
|
+
if (data.type === "conversation.item.input_audio_transcription.completed") {
|
|
93
|
+
onTranscript(data.transcript || data.text || "", true);
|
|
94
|
+
}
|
|
95
|
+
if (data.type === "session.finished") {
|
|
96
|
+
if (data.transcript) {
|
|
97
|
+
onTranscript(data.transcript, true);
|
|
98
|
+
}
|
|
99
|
+
ws.close(1e3, "ASR finished");
|
|
100
|
+
}
|
|
101
|
+
if (data.type === "error") {
|
|
102
|
+
stop();
|
|
103
|
+
reject(new Error(data.error?.message || "ASR error"));
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
ws.onerror = () => reject(new Error("ASR connection failed"));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
30
109
|
function createASRClient(config) {
|
|
31
110
|
const {
|
|
32
111
|
// audioFormat = 'pcm16',
|
|
@@ -161,6 +240,7 @@ function createASRClient(config) {
|
|
|
161
240
|
// Annotate the CommonJS export names for ESM import in node:
|
|
162
241
|
0 && (module.exports = {
|
|
163
242
|
createASRClient,
|
|
164
|
-
createAsrClient
|
|
243
|
+
createAsrClient,
|
|
244
|
+
listen
|
|
165
245
|
});
|
|
166
246
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/asr-client.ts"],"sourcesContent":["export type { ASRClient, ASRClientConfig } from './asr-client';\nexport { createASRClient } 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\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: 'pcm16' | 'g711a' | 'g711u';\n /** Sample rate, default 16000 */\n sampleRate?: number;\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}\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 // audioFormat = 'pcm16',\n sampleRate = 16000,\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n ws = new WebSocket(ASR_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 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 || '', 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 || '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 startRecording(): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('Recording only supported in browser');\n }\n\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket not connected');\n }\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n\n const inputData = e.inputBuffer.getChannelData(0);\n const inputLen = inputData.length;\n\n const pcm = new Int16Array(inputLen);\n for (let i = 0; i < inputLen; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n const len = bytes.length;\n let binary = '';\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n const base64 = btoa(binary);\n\n ws.send(JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64,\n }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function stopRecording() {\n if (mediaStream) {\n mediaStream.getTracks().forEach(track => track.stop());\n mediaStream = null;\n }\n\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\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 if (ws) {\n ws.close();\n ws = null;\n }\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"],"sourcesContent":["export { listen } from './asr-client';\nexport type { ASRClient, ASRClientConfig } from './asr-client';\nexport { createASRClient, 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\n/**\n * Simple ASR: start listening and get transcript\n * @returns stop function\n * @example\n * const stop = await listen((text, isFinal) => console.log(text))\n * // later: stop()\n */\nexport async function listen(\n onTranscript: (text: string, isFinal: boolean) => void\n): Promise<() => void> {\n // Check browser support first\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not supported. Requires HTTPS and browser with MediaDevices API.');\n }\n\n const ws = new WebSocket(ASR_PATH);\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let isRunning = true;\n\n const stop = () => {\n isRunning = false;\n if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; }\n if (processor) { processor.disconnect(); processor = null; }\n if (audioContext) { audioContext.close(); audioContext = null; }\n if (ws.readyState === WebSocket.OPEN) {\n // Send session.finish to get final result\n ws.send(JSON.stringify({ event_id: `event_${Date.now()}`, type: 'session.finish' }));\n }\n };\n\n return new Promise((resolve, reject) => {\n ws.onmessage = async (event) => {\n const data = JSON.parse(event.data);\n\n // Start recording immediately on session.created (use default config)\n if (data.type === 'session.created') {\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true }\n });\n audioContext = new AudioContext({ sampleRate: 16000 });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!isRunning || ws.readyState !== WebSocket.OPEN) return;\n const input = e.inputBuffer.getChannelData(0);\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]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n const bytes = new Uint8Array(pcm.buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: btoa(binary) }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n resolve(stop);\n } catch (err) {\n reject(err);\n }\n }\n\n // Interim result\n if (data.type === 'conversation.item.input_audio_transcription.text') {\n onTranscript(data.text || data.stash || '', false);\n }\n\n // Sentence complete (VAD detected pause)\n if (data.type === 'conversation.item.input_audio_transcription.completed') {\n onTranscript(data.transcript || data.text || '', true);\n }\n\n // Final result after session.finish\n if (data.type === 'session.finished') {\n if (data.transcript) {\n onTranscript(data.transcript, true);\n }\n ws.close(1000, 'ASR finished');\n }\n\n if (data.type === 'error') {\n stop();\n reject(new Error(data.error?.message || 'ASR error'));\n }\n };\n\n ws.onerror = () => reject(new Error('ASR connection failed'));\n });\n}\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: 'pcm16' | 'g711a' | 'g711u';\n /** Sample rate, default 16000 */\n sampleRate?: number;\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}\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 // audioFormat = 'pcm16',\n sampleRate = 16000,\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n ws = new WebSocket(ASR_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 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 || '', 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 || '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 startRecording(): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('Recording only supported in browser');\n }\n\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket not connected');\n }\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n\n const inputData = e.inputBuffer.getChannelData(0);\n const inputLen = inputData.length;\n\n const pcm = new Int16Array(inputLen);\n for (let i = 0; i < inputLen; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n const len = bytes.length;\n let binary = '';\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n const base64 = btoa(binary);\n\n ws.send(JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64,\n }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function stopRecording() {\n if (mediaStream) {\n mediaStream.getTracks().forEach(track => track.stop());\n mediaStream = null;\n }\n\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\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 if (ws) {\n ws.close();\n ws = null;\n }\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ASR Realtime WebSocket Client
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Simple ASR: start listening and get transcript
|
|
6
|
+
* @returns stop function
|
|
7
|
+
* @example
|
|
8
|
+
* const stop = await listen((text, isFinal) => console.log(text))
|
|
9
|
+
* // later: stop()
|
|
10
|
+
*/
|
|
11
|
+
declare function listen(onTranscript: (text: string, isFinal: boolean) => void): Promise<() => void>;
|
|
4
12
|
interface ASRClientConfig {
|
|
5
13
|
/** Audio format, default 'pcm16' */
|
|
6
14
|
audioFormat?: 'pcm16' | 'g711a' | 'g711u';
|
|
@@ -29,4 +37,4 @@ interface ASRClient {
|
|
|
29
37
|
}
|
|
30
38
|
declare function createASRClient(config: ASRClientConfig): ASRClient;
|
|
31
39
|
|
|
32
|
-
export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient };
|
|
40
|
+
export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient, listen };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ASR Realtime WebSocket Client
|
|
3
3
|
*/
|
|
4
|
+
/**
|
|
5
|
+
* Simple ASR: start listening and get transcript
|
|
6
|
+
* @returns stop function
|
|
7
|
+
* @example
|
|
8
|
+
* const stop = await listen((text, isFinal) => console.log(text))
|
|
9
|
+
* // later: stop()
|
|
10
|
+
*/
|
|
11
|
+
declare function listen(onTranscript: (text: string, isFinal: boolean) => void): Promise<() => void>;
|
|
4
12
|
interface ASRClientConfig {
|
|
5
13
|
/** Audio format, default 'pcm16' */
|
|
6
14
|
audioFormat?: 'pcm16' | 'g711a' | 'g711u';
|
|
@@ -29,4 +37,4 @@ interface ASRClient {
|
|
|
29
37
|
}
|
|
30
38
|
declare function createASRClient(config: ASRClientConfig): ASRClient;
|
|
31
39
|
|
|
32
|
-
export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient };
|
|
40
|
+
export { type ASRClient, type ASRClientConfig, createASRClient, createASRClient as createAsrClient, listen };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
// src/asr-client.ts
|
|
2
2
|
var ASR_PATH = "/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime";
|
|
3
|
+
async function listen(onTranscript) {
|
|
4
|
+
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
5
|
+
throw new Error("Microphone not supported. Requires HTTPS and browser with MediaDevices API.");
|
|
6
|
+
}
|
|
7
|
+
const ws = new WebSocket(ASR_PATH);
|
|
8
|
+
let mediaStream = null;
|
|
9
|
+
let audioContext = null;
|
|
10
|
+
let processor = null;
|
|
11
|
+
let isRunning = true;
|
|
12
|
+
const stop = () => {
|
|
13
|
+
isRunning = false;
|
|
14
|
+
if (mediaStream) {
|
|
15
|
+
mediaStream.getTracks().forEach((t) => t.stop());
|
|
16
|
+
mediaStream = null;
|
|
17
|
+
}
|
|
18
|
+
if (processor) {
|
|
19
|
+
processor.disconnect();
|
|
20
|
+
processor = null;
|
|
21
|
+
}
|
|
22
|
+
if (audioContext) {
|
|
23
|
+
audioContext.close();
|
|
24
|
+
audioContext = null;
|
|
25
|
+
}
|
|
26
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
27
|
+
ws.send(JSON.stringify({ event_id: `event_${Date.now()}`, type: "session.finish" }));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
ws.onmessage = async (event) => {
|
|
32
|
+
const data = JSON.parse(event.data);
|
|
33
|
+
if (data.type === "session.created") {
|
|
34
|
+
try {
|
|
35
|
+
mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
36
|
+
audio: { sampleRate: 16e3, channelCount: 1, echoCancellation: true }
|
|
37
|
+
});
|
|
38
|
+
audioContext = new AudioContext({ sampleRate: 16e3 });
|
|
39
|
+
const source = audioContext.createMediaStreamSource(mediaStream);
|
|
40
|
+
processor = audioContext.createScriptProcessor(4096, 1, 1);
|
|
41
|
+
processor.onaudioprocess = (e) => {
|
|
42
|
+
if (!isRunning || ws.readyState !== WebSocket.OPEN) return;
|
|
43
|
+
const input = e.inputBuffer.getChannelData(0);
|
|
44
|
+
const pcm = new Int16Array(input.length);
|
|
45
|
+
for (let i = 0; i < input.length; i++) {
|
|
46
|
+
const s = Math.max(-1, Math.min(1, input[i]));
|
|
47
|
+
pcm[i] = s < 0 ? s * 32768 : s * 32767;
|
|
48
|
+
}
|
|
49
|
+
const bytes = new Uint8Array(pcm.buffer);
|
|
50
|
+
let binary = "";
|
|
51
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
52
|
+
ws.send(JSON.stringify({ type: "input_audio_buffer.append", audio: btoa(binary) }));
|
|
53
|
+
};
|
|
54
|
+
source.connect(processor);
|
|
55
|
+
processor.connect(audioContext.destination);
|
|
56
|
+
resolve(stop);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
reject(err);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (data.type === "conversation.item.input_audio_transcription.text") {
|
|
62
|
+
onTranscript(data.text || data.stash || "", false);
|
|
63
|
+
}
|
|
64
|
+
if (data.type === "conversation.item.input_audio_transcription.completed") {
|
|
65
|
+
onTranscript(data.transcript || data.text || "", true);
|
|
66
|
+
}
|
|
67
|
+
if (data.type === "session.finished") {
|
|
68
|
+
if (data.transcript) {
|
|
69
|
+
onTranscript(data.transcript, true);
|
|
70
|
+
}
|
|
71
|
+
ws.close(1e3, "ASR finished");
|
|
72
|
+
}
|
|
73
|
+
if (data.type === "error") {
|
|
74
|
+
stop();
|
|
75
|
+
reject(new Error(data.error?.message || "ASR error"));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
ws.onerror = () => reject(new Error("ASR connection failed"));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
3
81
|
function createASRClient(config) {
|
|
4
82
|
const {
|
|
5
83
|
// audioFormat = 'pcm16',
|
|
@@ -133,6 +211,7 @@ function createASRClient(config) {
|
|
|
133
211
|
}
|
|
134
212
|
export {
|
|
135
213
|
createASRClient,
|
|
136
|
-
createASRClient as createAsrClient
|
|
214
|
+
createASRClient as createAsrClient,
|
|
215
|
+
listen
|
|
137
216
|
};
|
|
138
217
|
//# 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\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: 'pcm16' | 'g711a' | 'g711u';\n /** Sample rate, default 16000 */\n sampleRate?: number;\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}\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 // audioFormat = 'pcm16',\n sampleRate = 16000,\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n ws = new WebSocket(ASR_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 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 || '', 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 || '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 startRecording(): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('Recording only supported in browser');\n }\n\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket not connected');\n }\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n\n const inputData = e.inputBuffer.getChannelData(0);\n const inputLen = inputData.length;\n\n const pcm = new Int16Array(inputLen);\n for (let i = 0; i < inputLen; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n const len = bytes.length;\n let binary = '';\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n const base64 = btoa(binary);\n\n ws.send(JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64,\n }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function stopRecording() {\n if (mediaStream) {\n mediaStream.getTracks().forEach(track => track.stop());\n mediaStream = null;\n }\n\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\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 if (ws) {\n ws.close();\n ws = null;\n }\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"],"sourcesContent":["/**\n * ASR Realtime WebSocket Client\n */\n\nconst ASR_PATH = '/api/proxy/builtin/platform/qwen-asr-realtime/api-ws/v1/realtime';\n\n/**\n * Simple ASR: start listening and get transcript\n * @returns stop function\n * @example\n * const stop = await listen((text, isFinal) => console.log(text))\n * // later: stop()\n */\nexport async function listen(\n onTranscript: (text: string, isFinal: boolean) => void\n): Promise<() => void> {\n // Check browser support first\n if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {\n throw new Error('Microphone not supported. Requires HTTPS and browser with MediaDevices API.');\n }\n\n const ws = new WebSocket(ASR_PATH);\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n let isRunning = true;\n\n const stop = () => {\n isRunning = false;\n if (mediaStream) { mediaStream.getTracks().forEach(t => t.stop()); mediaStream = null; }\n if (processor) { processor.disconnect(); processor = null; }\n if (audioContext) { audioContext.close(); audioContext = null; }\n if (ws.readyState === WebSocket.OPEN) {\n // Send session.finish to get final result\n ws.send(JSON.stringify({ event_id: `event_${Date.now()}`, type: 'session.finish' }));\n }\n };\n\n return new Promise((resolve, reject) => {\n ws.onmessage = async (event) => {\n const data = JSON.parse(event.data);\n\n // Start recording immediately on session.created (use default config)\n if (data.type === 'session.created') {\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true }\n });\n audioContext = new AudioContext({ sampleRate: 16000 });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!isRunning || ws.readyState !== WebSocket.OPEN) return;\n const input = e.inputBuffer.getChannelData(0);\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]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n const bytes = new Uint8Array(pcm.buffer);\n let binary = '';\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!);\n ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: btoa(binary) }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n resolve(stop);\n } catch (err) {\n reject(err);\n }\n }\n\n // Interim result\n if (data.type === 'conversation.item.input_audio_transcription.text') {\n onTranscript(data.text || data.stash || '', false);\n }\n\n // Sentence complete (VAD detected pause)\n if (data.type === 'conversation.item.input_audio_transcription.completed') {\n onTranscript(data.transcript || data.text || '', true);\n }\n\n // Final result after session.finish\n if (data.type === 'session.finished') {\n if (data.transcript) {\n onTranscript(data.transcript, true);\n }\n ws.close(1000, 'ASR finished');\n }\n\n if (data.type === 'error') {\n stop();\n reject(new Error(data.error?.message || 'ASR error'));\n }\n };\n\n ws.onerror = () => reject(new Error('ASR connection failed'));\n });\n}\n\nexport interface ASRClientConfig {\n /** Audio format, default 'pcm16' */\n audioFormat?: 'pcm16' | 'g711a' | 'g711u';\n /** Sample rate, default 16000 */\n sampleRate?: number;\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}\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 // audioFormat = 'pcm16',\n sampleRate = 16000,\n onReady,\n onSpeechStart,\n onSpeechEnd,\n onTranscript,\n onError,\n } = config;\n\n let ws: WebSocket | null = null;\n let mediaStream: MediaStream | null = null;\n let audioContext: AudioContext | null = null;\n let processor: ScriptProcessorNode | null = null;\n\n async function connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n ws = new WebSocket(ASR_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 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 || '', 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 || '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 startRecording(): Promise<void> {\n if (typeof window === 'undefined') {\n throw new Error('Recording only supported in browser');\n }\n\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n throw new Error('WebSocket not connected');\n }\n\n try {\n mediaStream = await navigator.mediaDevices.getUserMedia({\n audio: {\n sampleRate,\n channelCount: 1,\n echoCancellation: true,\n noiseSuppression: true,\n },\n });\n\n audioContext = new AudioContext({ sampleRate });\n const source = audioContext.createMediaStreamSource(mediaStream);\n processor = audioContext.createScriptProcessor(4096, 1, 1);\n\n processor.onaudioprocess = (e) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) return;\n\n const inputData = e.inputBuffer.getChannelData(0);\n const inputLen = inputData.length;\n\n const pcm = new Int16Array(inputLen);\n for (let i = 0; i < inputLen; i++) {\n const s = Math.max(-1, Math.min(1, inputData[i]!));\n pcm[i] = s < 0 ? s * 32768 : s * 32767;\n }\n\n const bytes = new Uint8Array(pcm.buffer);\n const len = bytes.length;\n let binary = '';\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n const base64 = btoa(binary);\n\n ws.send(JSON.stringify({\n type: 'input_audio_buffer.append',\n audio: base64,\n }));\n };\n\n source.connect(processor);\n processor.connect(audioContext.destination);\n } catch (err) {\n onError?.(err as Error);\n throw err;\n }\n }\n\n function stopRecording() {\n if (mediaStream) {\n mediaStream.getTracks().forEach(track => track.stop());\n mediaStream = null;\n }\n\n if (processor) {\n processor.disconnect();\n processor = null;\n }\n\n if (audioContext) {\n audioContext.close();\n audioContext = null;\n }\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 if (ws) {\n ws.close();\n ws = null;\n }\n }\n\n return {\n connect,\n startRecording,\n stopRecording,\n close,\n };\n}\n"],"mappings":"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","names":[]}
|