@easyv/biz-components 0.0.50 → 0.0.51

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.
@@ -22,10 +22,10 @@ export declare class AudioManager {
22
22
  mediaSource?: MediaStreamAudioSourceNode;
23
23
  mediaStream?: MediaStream;
24
24
  config: AudiManagerConfig;
25
- status: 'init' | 'starting' | 'started';
26
25
  constructor(config: AudiManagerConfig);
26
+ init(): Promise<void>;
27
27
  start(): Promise<void>;
28
28
  stop(): Promise<void>;
29
29
  close(): Promise<void> | undefined;
30
- destroy(): void;
30
+ destroy(): Promise<void>;
31
31
  }
@@ -17,49 +17,48 @@ class AudioManager {
17
17
  __publicField(this, "mediaSource");
18
18
  __publicField(this, "mediaStream");
19
19
  __publicField(this, "config", {});
20
- __publicField(this, "status", "init");
21
- console.log("audio manager constructor: ====", config);
22
20
  const processorStr = config.workletProcessor || defaultWorkletProcessorStr;
23
21
  this.workletUrl = generateWorkletUrl(processorStr);
24
22
  this.sampleRate = config.sampleRate;
25
23
  this.config = config;
26
24
  }
25
+ async init() {
26
+ if (this.audioContext) {
27
+ await this.destroy();
28
+ }
29
+ const audioContext = new window.AudioContext({
30
+ sampleRate: this.sampleRate
31
+ });
32
+ if (!this.workletUrl) {
33
+ throw Error("加载 workletProcessor 失败!");
34
+ }
35
+ await audioContext.audioWorklet.addModule(this.workletUrl);
36
+ const audioWorkletNode = new AudioWorkletNode(audioContext, "audio-worklet-processor");
37
+ const mediaStream = await requestMicrophonePermission();
38
+ const mediaSource = audioContext.createMediaStreamSource(mediaStream);
39
+ audioWorkletNode.port.postMessage({
40
+ type: "init",
41
+ frameSize: this.config.frameSize
42
+ });
43
+ audioWorkletNode.port.onmessage = (e) => {
44
+ var _a, _b;
45
+ (_b = (_a = this.config).onFrameRecorded) == null ? void 0 : _b.call(_a, e.data);
46
+ };
47
+ mediaSource.connect(audioWorkletNode);
48
+ this.audioContext = audioContext;
49
+ this.mediaSource = mediaSource;
50
+ this.mediaStream = mediaStream;
51
+ }
27
52
  async start() {
28
- var _a, _b, _c;
53
+ var _a, _b, _c, _d;
29
54
  try {
30
- console.log(
31
- "%c 🐶 dog ==== audio manager start:",
32
- "color: green; font-size: 16px;",
33
- this.audioContext
34
- );
35
- if (((_a = this.audioContext) == null ? void 0 : _a.state) === "running" || this.status === "starting") {
36
- return;
55
+ if (((_a = this.audioContext) == null ? void 0 : _a.state) === "suspended") {
56
+ this.audioContext.resume();
37
57
  }
38
- this.status = "starting";
39
- const audioContext = new window.AudioContext({
40
- sampleRate: this.sampleRate
41
- });
42
- if (!this.workletUrl) {
43
- throw Error("加载 workletProcessor 失败!");
58
+ if (((_b = this.audioContext) == null ? void 0 : _b.state) === "closed") {
59
+ await this.init();
44
60
  }
45
- await audioContext.audioWorklet.addModule(this.workletUrl);
46
- const audioWorkletNode = new AudioWorkletNode(audioContext, "audio-worklet-processor");
47
- const mediaStream = await requestMicrophonePermission();
48
- const mediaSource = audioContext.createMediaStreamSource(mediaStream);
49
- audioWorkletNode.port.postMessage({
50
- type: "init",
51
- frameSize: this.config.frameSize
52
- });
53
- audioWorkletNode.port.onmessage = (e) => {
54
- var _a2, _b2;
55
- (_b2 = (_a2 = this.config).onFrameRecorded) == null ? void 0 : _b2.call(_a2, e.data);
56
- };
57
- mediaSource.connect(audioWorkletNode);
58
- this.audioContext = audioContext;
59
- this.mediaSource = mediaSource;
60
- this.mediaStream = mediaStream;
61
- (_c = (_b = this.config).onStart) == null ? void 0 : _c.call(_b);
62
- this.status = "started";
61
+ (_d = (_c = this.config).onStart) == null ? void 0 : _d.call(_c);
63
62
  } catch (error) {
64
63
  console.error("Failed to start recording:", error);
65
64
  throw error;
@@ -73,15 +72,15 @@ class AudioManager {
73
72
  (_c = (_b = this.config).onStop) == null ? void 0 : _c.call(_b);
74
73
  }
75
74
  close() {
76
- var _a, _b, _c;
75
+ var _a;
77
76
  (_a = this.mediaSource) == null ? void 0 : _a.disconnect();
78
- if (((_b = this.audioContext) == null ? void 0 : _b.state) !== "closed") {
79
- return (_c = this.audioContext) == null ? void 0 : _c.close();
77
+ if (this.audioContext && this.audioContext.state !== "closed") {
78
+ return this.audioContext.close();
80
79
  }
81
80
  }
82
- destroy() {
81
+ async destroy() {
83
82
  var _a, _b;
84
- this.close();
83
+ await this.close();
85
84
  (_b = (_a = this.mediaStream) == null ? void 0 : _a.getTracks()) == null ? void 0 : _b.forEach((track) => track.stop());
86
85
  }
87
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AudioManager.es.js","sources":["../../../../src/utils/voskBrowserManager/AudioManager/AudioManager.ts"],"sourcesContent":["import { requestMicrophonePermission } from '../../xunFeiVoice/utils';\nimport { SampleRate } from './types';\nimport defaultWorkletProcessorStr from './workletProcessor.js?raw';\n\nexport const defaultSampleRate = 48000;\n\nexport interface AudiManagerConfig {\n /** 一个转换成 string js 文件。其内容要符合 AudioWorkletProcessor 的要求。\n * - 需使用 registerProcessor 注册,第一个参数为 “audio-worklet-processor”,如: registerProcessor('audio-worklet-processor', audioToFloat32Processor);\n * - 参考:https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor\n */\n workletProcessor?: string;\n /** 采样率(每秒采样数)的浮点数,常用值:16000, 32000, 44100, 48000 */\n sampleRate?: SampleRate;\n onFrameRecorded?: (data: Float32Array) => void;\n onStart?: () => void;\n onStop?: () => void;\n frameSize?: number;\n}\n\nconst generateWorkletUrl = (jsStr: string) => {\n const blob = new Blob([jsStr], { type: 'application/javascript' });\n const scriptUrl = URL.createObjectURL(blob);\n return scriptUrl;\n};\n\n/** 对实时音频录制进行管理。 */\nexport class AudioManager {\n workletUrl?: string;\n sampleRate?: SampleRate = defaultSampleRate;\n audioContext?: AudioContext;\n mediaSource?: MediaStreamAudioSourceNode;\n mediaStream?: MediaStream;\n config: AudiManagerConfig = {};\n status: 'init' | 'starting' | 'started' = 'init';\n\n constructor(config: AudiManagerConfig) {\n console.log('audio manager constructor: ====', config);\n const processorStr = config.workletProcessor || defaultWorkletProcessorStr;\n this.workletUrl = generateWorkletUrl(processorStr);\n this.sampleRate = config.sampleRate;\n this.config = config;\n }\n\n async start() {\n try {\n console.log(\n '%c 🐶 dog ==== audio manager start:',\n 'color: green; font-size: 16px;',\n this.audioContext,\n );\n if (this.audioContext?.state === 'running' || this.status === 'starting') {\n return;\n }\n this.status = 'starting';\n // 1. 创建 AudioContext\n const audioContext = new window.AudioContext({\n sampleRate: this.sampleRate,\n });\n if (!this.workletUrl) {\n throw Error('加载 workletProcessor 失败!');\n }\n // 加载给定 JavaScript 文件中的模块,并将其添加到当前 Worklet 中\n await audioContext.audioWorklet.addModule(this.workletUrl);\n const audioWorkletNode = new AudioWorkletNode(audioContext, 'audio-worklet-processor');\n // 获取音频流并创建处理节点\n const mediaStream = await requestMicrophonePermission();\n const mediaSource = audioContext.createMediaStreamSource(mediaStream);\n // 发送配置给 workletProcessor\n audioWorkletNode.port.postMessage({\n type: 'init',\n frameSize: this.config.frameSize,\n });\n // 监听消息\n audioWorkletNode.port.onmessage = (e) => {\n this.config.onFrameRecorded?.(e.data);\n };\n // 连接音频节点\n mediaSource.connect(audioWorkletNode);\n // 赋值到 this 上\n this.audioContext = audioContext;\n this.mediaSource = mediaSource;\n this.mediaStream = mediaStream;\n this.config.onStart?.();\n this.status = 'started';\n } catch (error) {\n console.error('Failed to start recording:', error);\n throw error;\n }\n }\n\n async stop() {\n /** 暂停音频上下文对象中的进度,并暂时剥离进程对音频设备硬件的访问权限,减少 CPU 和电池的使用 */\n if (this.audioContext?.state === 'running') {\n await this.audioContext.suspend();\n }\n this.config.onStop?.();\n }\n\n close() {\n this.mediaSource?.disconnect();\n if (this.audioContext?.state !== 'closed') {\n return this.audioContext?.close();\n }\n }\n\n destroy() {\n this.close();\n this.mediaStream?.getTracks()?.forEach((track) => track.stop());\n }\n}\n"],"names":["_b","_a"],"mappings":";;;;;AAIO,MAAM,oBAAoB;AAgBjC,MAAM,qBAAqB,CAAC,UAAkB;AACtC,QAAA,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,0BAA0B;AAC3D,QAAA,YAAY,IAAI,gBAAgB,IAAI;AACnC,SAAA;AACT;AAGO,MAAM,aAAa;AAAA,EASxB,YAAY,QAA2B;AARvC;AACA,sCAA0B;AAC1B;AACA;AACA;AACA,kCAA4B,CAAC;AAC7B,kCAA0C;AAGhC,YAAA,IAAI,mCAAmC,MAAM;AAC/C,UAAA,eAAe,OAAO,oBAAoB;AAC3C,SAAA,aAAa,mBAAmB,YAAY;AACjD,SAAK,aAAa,OAAO;AACzB,SAAK,SAAS;AAAA,EAAA;AAAA,EAGhB,MAAM,QAAQ;;AACR,QAAA;AACM,cAAA;AAAA,QACN;AAAA,QACA;AAAA,QACA,KAAK;AAAA,MACP;AACA,YAAI,UAAK,iBAAL,mBAAmB,WAAU,aAAa,KAAK,WAAW,YAAY;AACxE;AAAA,MAAA;AAEF,WAAK,SAAS;AAER,YAAA,eAAe,IAAI,OAAO,aAAa;AAAA,QAC3C,YAAY,KAAK;AAAA,MAAA,CAClB;AACG,UAAA,CAAC,KAAK,YAAY;AACpB,cAAM,MAAM,yBAAyB;AAAA,MAAA;AAGvC,YAAM,aAAa,aAAa,UAAU,KAAK,UAAU;AACzD,YAAM,mBAAmB,IAAI,iBAAiB,cAAc,yBAAyB;AAE/E,YAAA,cAAc,MAAM,4BAA4B;AAChD,YAAA,cAAc,aAAa,wBAAwB,WAAW;AAEpE,uBAAiB,KAAK,YAAY;AAAA,QAChC,MAAM;AAAA,QACN,WAAW,KAAK,OAAO;AAAA,MAAA,CACxB;AAEgB,uBAAA,KAAK,YAAY,CAAC,MAAM;;AAClC,SAAAA,OAAAC,MAAA,KAAA,QAAO,oBAAP,gBAAAD,IAAA,KAAAC,KAAyB,EAAE;AAAA,MAClC;AAEA,kBAAY,QAAQ,gBAAgB;AAEpC,WAAK,eAAe;AACpB,WAAK,cAAc;AACnB,WAAK,cAAc;AACnB,uBAAK,QAAO,YAAZ;AACA,WAAK,SAAS;AAAA,aACP,OAAO;AACN,cAAA,MAAM,8BAA8B,KAAK;AAC3C,YAAA;AAAA,IAAA;AAAA,EACR;AAAA,EAGF,MAAM,OAAO;;AAEP,UAAA,UAAK,iBAAL,mBAAmB,WAAU,WAAW;AACpC,YAAA,KAAK,aAAa,QAAQ;AAAA,IAAA;AAElC,qBAAK,QAAO,WAAZ;AAAA,EAAqB;AAAA,EAGvB,QAAQ;;AACN,eAAK,gBAAL,mBAAkB;AACd,UAAA,UAAK,iBAAL,mBAAmB,WAAU,UAAU;AAClC,cAAA,UAAK,iBAAL,mBAAmB;AAAA,IAAM;AAAA,EAClC;AAAA,EAGF,UAAU;;AACR,SAAK,MAAM;AACN,qBAAA,gBAAA,mBAAa,gBAAb,mBAA0B,QAAQ,CAAC,UAAU,MAAM;EAAM;AAElE;"}
1
+ {"version":3,"file":"AudioManager.es.js","sources":["../../../../src/utils/voskBrowserManager/AudioManager/AudioManager.ts"],"sourcesContent":["import { requestMicrophonePermission } from '../../xunFeiVoice/utils';\nimport { SampleRate } from './types';\nimport defaultWorkletProcessorStr from './workletProcessor.js?raw';\n\nexport const defaultSampleRate = 48000;\n\nexport interface AudiManagerConfig {\n /** 一个转换成 string js 文件。其内容要符合 AudioWorkletProcessor 的要求。\n * - 需使用 registerProcessor 注册,第一个参数为 “audio-worklet-processor”,如: registerProcessor('audio-worklet-processor', audioToFloat32Processor);\n * - 参考:https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor\n */\n workletProcessor?: string;\n /** 采样率(每秒采样数)的浮点数,常用值:16000, 32000, 44100, 48000 */\n sampleRate?: SampleRate;\n onFrameRecorded?: (data: Float32Array) => void;\n onStart?: () => void;\n onStop?: () => void;\n frameSize?: number;\n}\n\nconst generateWorkletUrl = (jsStr: string) => {\n const blob = new Blob([jsStr], { type: 'application/javascript' });\n const scriptUrl = URL.createObjectURL(blob);\n return scriptUrl;\n};\n\n/** 对实时音频录制进行管理。 */\nexport class AudioManager {\n workletUrl?: string;\n sampleRate?: SampleRate = defaultSampleRate;\n audioContext?: AudioContext;\n mediaSource?: MediaStreamAudioSourceNode;\n mediaStream?: MediaStream;\n config: AudiManagerConfig = {};\n\n constructor(config: AudiManagerConfig) {\n const processorStr = config.workletProcessor || defaultWorkletProcessorStr;\n this.workletUrl = generateWorkletUrl(processorStr);\n this.sampleRate = config.sampleRate;\n this.config = config;\n }\n\n async init() {\n if (this.audioContext) {\n await this.destroy();\n }\n // 1. 创建 AudioContext\n const audioContext = new window.AudioContext({\n sampleRate: this.sampleRate,\n });\n if (!this.workletUrl) {\n throw Error('加载 workletProcessor 失败!');\n }\n // 加载给定 JavaScript 文件中的模块,并将其添加到当前 Worklet 中\n await audioContext.audioWorklet.addModule(this.workletUrl);\n const audioWorkletNode = new AudioWorkletNode(audioContext, 'audio-worklet-processor');\n // 获取音频流并创建处理节点\n const mediaStream = await requestMicrophonePermission();\n const mediaSource = audioContext.createMediaStreamSource(mediaStream);\n // 发送配置给 workletProcessor\n audioWorkletNode.port.postMessage({\n type: 'init',\n frameSize: this.config.frameSize,\n });\n // 监听消息\n audioWorkletNode.port.onmessage = (e) => {\n this.config.onFrameRecorded?.(e.data);\n };\n // 连接音频节点\n mediaSource.connect(audioWorkletNode);\n // 赋值到 this 上\n this.audioContext = audioContext;\n this.mediaSource = mediaSource;\n this.mediaStream = mediaStream;\n }\n\n async start() {\n try {\n if (this.audioContext?.state === 'suspended') {\n this.audioContext.resume();\n }\n if (this.audioContext?.state === 'closed') {\n await this.init();\n }\n this.config.onStart?.();\n } catch (error) {\n console.error('Failed to start recording:', error);\n throw error;\n }\n }\n\n async stop() {\n /** 暂停音频上下文对象中的进度,并暂时剥离进程对音频设备硬件的访问权限,减少 CPU 和电池的使用 */\n if (this.audioContext?.state === 'running') {\n await this.audioContext.suspend();\n }\n this.config.onStop?.();\n }\n\n close() {\n this.mediaSource?.disconnect();\n if (this.audioContext && this.audioContext.state !== 'closed') {\n return this.audioContext.close();\n }\n }\n\n async destroy() {\n await this.close();\n this.mediaStream?.getTracks()?.forEach((track) => track.stop());\n }\n}\n"],"names":[],"mappings":";;;;;AAIO,MAAM,oBAAoB;AAgBjC,MAAM,qBAAqB,CAAC,UAAkB;AACtC,QAAA,OAAO,IAAI,KAAK,CAAC,KAAK,GAAG,EAAE,MAAM,0BAA0B;AAC3D,QAAA,YAAY,IAAI,gBAAgB,IAAI;AACnC,SAAA;AACT;AAGO,MAAM,aAAa;AAAA,EAQxB,YAAY,QAA2B;AAPvC;AACA,sCAA0B;AAC1B;AACA;AACA;AACA,kCAA4B,CAAC;AAGrB,UAAA,eAAe,OAAO,oBAAoB;AAC3C,SAAA,aAAa,mBAAmB,YAAY;AACjD,SAAK,aAAa,OAAO;AACzB,SAAK,SAAS;AAAA,EAAA;AAAA,EAGhB,MAAM,OAAO;AACX,QAAI,KAAK,cAAc;AACrB,YAAM,KAAK,QAAQ;AAAA,IAAA;AAGf,UAAA,eAAe,IAAI,OAAO,aAAa;AAAA,MAC3C,YAAY,KAAK;AAAA,IAAA,CAClB;AACG,QAAA,CAAC,KAAK,YAAY;AACpB,YAAM,MAAM,yBAAyB;AAAA,IAAA;AAGvC,UAAM,aAAa,aAAa,UAAU,KAAK,UAAU;AACzD,UAAM,mBAAmB,IAAI,iBAAiB,cAAc,yBAAyB;AAE/E,UAAA,cAAc,MAAM,4BAA4B;AAChD,UAAA,cAAc,aAAa,wBAAwB,WAAW;AAEpE,qBAAiB,KAAK,YAAY;AAAA,MAChC,MAAM;AAAA,MACN,WAAW,KAAK,OAAO;AAAA,IAAA,CACxB;AAEgB,qBAAA,KAAK,YAAY,CAAC,MAAM;;AAClC,uBAAA,QAAO,oBAAP,4BAAyB,EAAE;AAAA,IAClC;AAEA,gBAAY,QAAQ,gBAAgB;AAEpC,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,cAAc;AAAA,EAAA;AAAA,EAGrB,MAAM,QAAQ;;AACR,QAAA;AACE,YAAA,UAAK,iBAAL,mBAAmB,WAAU,aAAa;AAC5C,aAAK,aAAa,OAAO;AAAA,MAAA;AAEvB,YAAA,UAAK,iBAAL,mBAAmB,WAAU,UAAU;AACzC,cAAM,KAAK,KAAK;AAAA,MAAA;AAElB,uBAAK,QAAO,YAAZ;AAAA,aACO,OAAO;AACN,cAAA,MAAM,8BAA8B,KAAK;AAC3C,YAAA;AAAA,IAAA;AAAA,EACR;AAAA,EAGF,MAAM,OAAO;;AAEP,UAAA,UAAK,iBAAL,mBAAmB,WAAU,WAAW;AACpC,YAAA,KAAK,aAAa,QAAQ;AAAA,IAAA;AAElC,qBAAK,QAAO,WAAZ;AAAA,EAAqB;AAAA,EAGvB,QAAQ;;AACN,eAAK,gBAAL,mBAAkB;AAClB,QAAI,KAAK,gBAAgB,KAAK,aAAa,UAAU,UAAU;AACtD,aAAA,KAAK,aAAa,MAAM;AAAA,IAAA;AAAA,EACjC;AAAA,EAGF,MAAM,UAAU;;AACd,UAAM,KAAK,MAAM;AACZ,qBAAA,gBAAA,mBAAa,gBAAb,mBAA0B,QAAQ,CAAC,UAAU,MAAM;EAAM;AAElE;"}
@@ -66,29 +66,23 @@ class VoskBrowserManager {
66
66
  (_b2 = (_a2 = this.config).onFrameRecorded) == null ? void 0 : _b2.call(_a2, data);
67
67
  }
68
68
  });
69
+ await this.audioManager.init();
69
70
  (_b = (_a = this.config).onInitialized) == null ? void 0 : _b.call(_a);
70
71
  (_c = this.startResolve) == null ? void 0 : _c.call(this);
71
72
  this.status = "initialized";
72
- console.log("%c 🐱 cat ==== init:", "color: orange; font-size: 16px;", this);
73
73
  if (this.autoStart) {
74
74
  this.start();
75
75
  }
76
76
  }
77
77
  async start() {
78
- var _a, _b, _c, _d;
79
- console.log(
80
- "vosk manager start ====",
81
- this.audioManager,
82
- (_b = (_a = this.audioManager) == null ? void 0 : _a.audioContext) == null ? void 0 : _b.state
83
- );
78
+ var _a, _b;
84
79
  if (this.status === "initialized") {
85
- await ((_c = this.audioManager) == null ? void 0 : _c.start());
80
+ await ((_a = this.audioManager) == null ? void 0 : _a.start());
86
81
  } else {
87
82
  await new Promise((resolve) => {
88
83
  this.startResolve = resolve;
89
84
  });
90
- console.log("%c 🐱 cat ==== start resolved", "color: orange; font-size: 16px;");
91
- await ((_d = this.audioManager) == null ? void 0 : _d.start());
85
+ await ((_b = this.audioManager) == null ? void 0 : _b.start());
92
86
  }
93
87
  }
94
88
  stop() {
@@ -1 +1 @@
1
- {"version":3,"file":"voskBrowserManager.es.js","sources":["../../../src/utils/voskBrowserManager/voskBrowserManager.ts"],"sourcesContent":["import { omit } from 'lodash-es';\nimport type * as Vosk from 'vosk-browser';\nimport { AudiManagerConfig, AudioManager, defaultSampleRate } from './AudioManager/AudioManager';\nimport { SampleRate } from './AudioManager/types';\n\ninterface VoskBrowserManagerConfig extends AudiManagerConfig {\n vosk: typeof Vosk;\n modelUrl: string;\n /** 加载模型等初始化配置完成后,自动开始监听 */\n autoStart?: boolean;\n onInitialized?: () => void;\n onResult?: (text: string) => void;\n onPartialResult?: (text: string) => void;\n}\n\nconst pickAudioManagerConfig = (config: VoskBrowserManagerConfig) => {\n return omit(config, [\n 'vosk',\n 'modelUrl',\n 'autoStart',\n 'onResult',\n 'onPartialResult',\n 'onInitialized',\n ]);\n};\n\nexport class VoskBrowserManager {\n config: VoskBrowserManagerConfig;\n /** 采样率(每秒采样数)的浮点数,常用值:16000, 32000, 44100, 48000 */\n sampleRate: SampleRate = defaultSampleRate;\n audioManager?: AudioManager;\n model?: Vosk.Model;\n autoStart = true;\n startResolve?: (value?: unknown) => void;\n status?: 'initializing' | 'initialized';\n\n constructor(config: VoskBrowserManagerConfig) {\n this.config = config;\n this.sampleRate = config.sampleRate || defaultSampleRate;\n this.autoStart = config.autoStart || true;\n this.init(config.vosk);\n }\n\n setConfig(config: Partial<Pick<VoskBrowserManagerConfig, 'onResult' | 'onPartialResult'>>) {\n this.config = {\n ...this.config,\n ...config,\n };\n }\n\n async init(vosk: typeof Vosk) {\n if (this.model) {\n this.model.terminate();\n }\n this.status = 'initializing';\n console.time('load model:');\n this.model = await vosk.createModel(this.config.modelUrl);\n console.timeEnd('load model:');\n\n const recognizer = new this.model.KaldiRecognizer(this.sampleRate);\n recognizer.on('result', (message) => {\n const resultText = (message as any).result.text;\n this.config.onResult?.(resultText);\n });\n recognizer.on('partialresult', (message) => {\n const partialText = (message as any).result.partial;\n this.config.onPartialResult?.(partialText);\n });\n\n if (this.audioManager) {\n this.audioManager.destroy();\n }\n this.audioManager = new AudioManager({\n ...pickAudioManagerConfig(this.config),\n sampleRate: this.sampleRate,\n onFrameRecorded: (data) => {\n recognizer.acceptWaveformFloat(data, this.sampleRate);\n this.config.onFrameRecorded?.(data);\n },\n });\n\n this.config.onInitialized?.();\n this.startResolve?.();\n this.status = 'initialized';\n console.log('%c 🐱 cat ==== init:', 'color: orange; font-size: 16px;', this);\n if (this.autoStart) {\n this.start();\n }\n }\n\n async start() {\n console.log(\n 'vosk manager start ====',\n this.audioManager,\n this.audioManager?.audioContext?.state,\n );\n if (this.status === 'initialized') {\n await this.audioManager?.start();\n } else {\n await new Promise((resolve) => {\n this.startResolve = resolve;\n });\n console.log('%c 🐱 cat ==== start resolved', 'color: orange; font-size: 16px;');\n await this.audioManager?.start();\n }\n }\n\n stop() {\n this.audioManager?.stop();\n }\n\n destroy() {\n this.audioManager?.destroy();\n this.model?.terminate();\n }\n}\n"],"names":["_b","_a"],"mappings":";;;;;AAeA,MAAM,yBAAyB,CAAC,WAAqC;AACnE,SAAO,KAAK,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AACH;AAEO,MAAM,mBAAmB;AAAA,EAU9B,YAAY,QAAkC;AAT9C;AAEA;AAAA,sCAAyB;AACzB;AACA;AACA,qCAAY;AACZ;AACA;AAGE,SAAK,SAAS;AACT,SAAA,aAAa,OAAO,cAAc;AAClC,SAAA,YAAY,OAAO,aAAa;AAChC,SAAA,KAAK,OAAO,IAAI;AAAA,EAAA;AAAA,EAGvB,UAAU,QAAiF;AACzF,SAAK,SAAS;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AAAA,EAAA;AAAA,EAGF,MAAM,KAAK,MAAmB;;AAC5B,QAAI,KAAK,OAAO;AACd,WAAK,MAAM,UAAU;AAAA,IAAA;AAEvB,SAAK,SAAS;AACd,YAAQ,KAAK,aAAa;AAC1B,SAAK,QAAQ,MAAM,KAAK,YAAY,KAAK,OAAO,QAAQ;AACxD,YAAQ,QAAQ,aAAa;AAE7B,UAAM,aAAa,IAAI,KAAK,MAAM,gBAAgB,KAAK,UAAU;AACtD,eAAA,GAAG,UAAU,CAAC,YAAY;;AAC7B,YAAA,aAAc,QAAgB,OAAO;AACtC,OAAAA,OAAAC,MAAA,KAAA,QAAO,aAAP,gBAAAD,IAAA,KAAAC,KAAkB;AAAA,IAAU,CAClC;AACU,eAAA,GAAG,iBAAiB,CAAC,YAAY;;AACpC,YAAA,cAAe,QAAgB,OAAO;AACvC,OAAAD,OAAAC,MAAA,KAAA,QAAO,oBAAP,gBAAAD,IAAA,KAAAC,KAAyB;AAAA,IAAW,CAC1C;AAED,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,QAAQ;AAAA,IAAA;AAEvB,SAAA,eAAe,IAAI,aAAa;AAAA,MACnC,GAAG,uBAAuB,KAAK,MAAM;AAAA,MACrC,YAAY,KAAK;AAAA,MACjB,iBAAiB,CAAC,SAAS;;AACd,mBAAA,oBAAoB,MAAM,KAAK,UAAU;AAC/C,SAAAD,OAAAC,MAAA,KAAA,QAAO,oBAAP,gBAAAD,IAAA,KAAAC,KAAyB;AAAA,MAAI;AAAA,IACpC,CACD;AAED,qBAAK,QAAO,kBAAZ;AACA,eAAK,iBAAL;AACA,SAAK,SAAS;AACN,YAAA,IAAI,0BAA0B,mCAAmC,IAAI;AAC7E,QAAI,KAAK,WAAW;AAClB,WAAK,MAAM;AAAA,IAAA;AAAA,EACb;AAAA,EAGF,MAAM,QAAQ;;AACJ,YAAA;AAAA,MACN;AAAA,MACA,KAAK;AAAA,OACL,gBAAK,iBAAL,mBAAmB,iBAAnB,mBAAiC;AAAA,IACnC;AACI,QAAA,KAAK,WAAW,eAAe;AAC3B,cAAA,UAAK,iBAAL,mBAAmB;AAAA,IAAM,OAC1B;AACC,YAAA,IAAI,QAAQ,CAAC,YAAY;AAC7B,aAAK,eAAe;AAAA,MAAA,CACrB;AACO,cAAA,IAAI,mCAAmC,iCAAiC;AAC1E,cAAA,UAAK,iBAAL,mBAAmB;AAAA,IAAM;AAAA,EACjC;AAAA,EAGF,OAAO;;AACL,eAAK,iBAAL,mBAAmB;AAAA,EAAK;AAAA,EAG1B,UAAU;;AACR,eAAK,iBAAL,mBAAmB;AACnB,eAAK,UAAL,mBAAY;AAAA,EAAU;AAE1B;"}
1
+ {"version":3,"file":"voskBrowserManager.es.js","sources":["../../../src/utils/voskBrowserManager/voskBrowserManager.ts"],"sourcesContent":["import { omit } from 'lodash-es';\nimport type * as Vosk from 'vosk-browser';\nimport { AudiManagerConfig, AudioManager, defaultSampleRate } from './AudioManager/AudioManager';\nimport { SampleRate } from './AudioManager/types';\n\ninterface VoskBrowserManagerConfig extends AudiManagerConfig {\n vosk: typeof Vosk;\n modelUrl: string;\n /** 加载模型等初始化配置完成后,自动开始监听 */\n autoStart?: boolean;\n onInitialized?: () => void;\n onResult?: (text: string) => void;\n onPartialResult?: (text: string) => void;\n}\n\nconst pickAudioManagerConfig = (config: VoskBrowserManagerConfig) => {\n return omit(config, [\n 'vosk',\n 'modelUrl',\n 'autoStart',\n 'onResult',\n 'onPartialResult',\n 'onInitialized',\n ]);\n};\n\nexport class VoskBrowserManager {\n config: VoskBrowserManagerConfig;\n /** 采样率(每秒采样数)的浮点数,常用值:16000, 32000, 44100, 48000 */\n sampleRate: SampleRate = defaultSampleRate;\n audioManager?: AudioManager;\n model?: Vosk.Model;\n autoStart = true;\n startResolve?: (value?: unknown) => void;\n status?: 'initializing' | 'initialized';\n\n constructor(config: VoskBrowserManagerConfig) {\n this.config = config;\n this.sampleRate = config.sampleRate || defaultSampleRate;\n this.autoStart = config.autoStart || true;\n this.init(config.vosk);\n }\n\n setConfig(config: Partial<Pick<VoskBrowserManagerConfig, 'onResult' | 'onPartialResult'>>) {\n this.config = {\n ...this.config,\n ...config,\n };\n }\n\n async init(vosk: typeof Vosk) {\n if (this.model) {\n this.model.terminate();\n }\n this.status = 'initializing';\n console.time('load model:');\n this.model = await vosk.createModel(this.config.modelUrl);\n console.timeEnd('load model:');\n\n const recognizer = new this.model.KaldiRecognizer(this.sampleRate);\n recognizer.on('result', (message) => {\n const resultText = (message as any).result.text;\n this.config.onResult?.(resultText);\n });\n recognizer.on('partialresult', (message) => {\n const partialText = (message as any).result.partial;\n this.config.onPartialResult?.(partialText);\n });\n\n if (this.audioManager) {\n this.audioManager.destroy();\n }\n this.audioManager = new AudioManager({\n ...pickAudioManagerConfig(this.config),\n sampleRate: this.sampleRate,\n onFrameRecorded: (data) => {\n recognizer.acceptWaveformFloat(data, this.sampleRate);\n this.config.onFrameRecorded?.(data);\n },\n });\n await this.audioManager.init();\n\n this.config.onInitialized?.();\n this.startResolve?.();\n this.status = 'initialized';\n if (this.autoStart) {\n this.start();\n }\n }\n\n async start() {\n if (this.status === 'initialized') {\n await this.audioManager?.start();\n } else {\n await new Promise((resolve) => {\n this.startResolve = resolve;\n });\n await this.audioManager?.start();\n }\n }\n\n stop() {\n this.audioManager?.stop();\n }\n\n destroy() {\n this.audioManager?.destroy();\n this.model?.terminate();\n }\n}\n"],"names":["_b","_a"],"mappings":";;;;;AAeA,MAAM,yBAAyB,CAAC,WAAqC;AACnE,SAAO,KAAK,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AACH;AAEO,MAAM,mBAAmB;AAAA,EAU9B,YAAY,QAAkC;AAT9C;AAEA;AAAA,sCAAyB;AACzB;AACA;AACA,qCAAY;AACZ;AACA;AAGE,SAAK,SAAS;AACT,SAAA,aAAa,OAAO,cAAc;AAClC,SAAA,YAAY,OAAO,aAAa;AAChC,SAAA,KAAK,OAAO,IAAI;AAAA,EAAA;AAAA,EAGvB,UAAU,QAAiF;AACzF,SAAK,SAAS;AAAA,MACZ,GAAG,KAAK;AAAA,MACR,GAAG;AAAA,IACL;AAAA,EAAA;AAAA,EAGF,MAAM,KAAK,MAAmB;;AAC5B,QAAI,KAAK,OAAO;AACd,WAAK,MAAM,UAAU;AAAA,IAAA;AAEvB,SAAK,SAAS;AACd,YAAQ,KAAK,aAAa;AAC1B,SAAK,QAAQ,MAAM,KAAK,YAAY,KAAK,OAAO,QAAQ;AACxD,YAAQ,QAAQ,aAAa;AAE7B,UAAM,aAAa,IAAI,KAAK,MAAM,gBAAgB,KAAK,UAAU;AACtD,eAAA,GAAG,UAAU,CAAC,YAAY;;AAC7B,YAAA,aAAc,QAAgB,OAAO;AACtC,OAAAA,OAAAC,MAAA,KAAA,QAAO,aAAP,gBAAAD,IAAA,KAAAC,KAAkB;AAAA,IAAU,CAClC;AACU,eAAA,GAAG,iBAAiB,CAAC,YAAY;;AACpC,YAAA,cAAe,QAAgB,OAAO;AACvC,OAAAD,OAAAC,MAAA,KAAA,QAAO,oBAAP,gBAAAD,IAAA,KAAAC,KAAyB;AAAA,IAAW,CAC1C;AAED,QAAI,KAAK,cAAc;AACrB,WAAK,aAAa,QAAQ;AAAA,IAAA;AAEvB,SAAA,eAAe,IAAI,aAAa;AAAA,MACnC,GAAG,uBAAuB,KAAK,MAAM;AAAA,MACrC,YAAY,KAAK;AAAA,MACjB,iBAAiB,CAAC,SAAS;;AACd,mBAAA,oBAAoB,MAAM,KAAK,UAAU;AAC/C,SAAAD,OAAAC,MAAA,KAAA,QAAO,oBAAP,gBAAAD,IAAA,KAAAC,KAAyB;AAAA,MAAI;AAAA,IACpC,CACD;AACK,UAAA,KAAK,aAAa,KAAK;AAE7B,qBAAK,QAAO,kBAAZ;AACA,eAAK,iBAAL;AACA,SAAK,SAAS;AACd,QAAI,KAAK,WAAW;AAClB,WAAK,MAAM;AAAA,IAAA;AAAA,EACb;AAAA,EAGF,MAAM,QAAQ;;AACR,QAAA,KAAK,WAAW,eAAe;AAC3B,cAAA,UAAK,iBAAL,mBAAmB;AAAA,IAAM,OAC1B;AACC,YAAA,IAAI,QAAQ,CAAC,YAAY;AAC7B,aAAK,eAAe;AAAA,MAAA,CACrB;AACK,cAAA,UAAK,iBAAL,mBAAmB;AAAA,IAAM;AAAA,EACjC;AAAA,EAGF,OAAO;;AACL,eAAK,iBAAL,mBAAmB;AAAA,EAAK;AAAA,EAG1B,UAAU;;AACR,eAAK,iBAAL,mBAAmB;AACnB,eAAK,UAAL,mBAAY;AAAA,EAAU;AAE1B;"}
@@ -3,7 +3,6 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { toBase64, checkMicrophonePermission, requestMicrophonePermission, isWsClosed } from "./utils.es.js";
5
5
  import { RecorderManager } from "./RecorderManager/RecorderManager.es.js";
6
- let startCount = 0;
7
6
  class XFStreamVoiceManager {
8
7
  constructor(config) {
9
8
  __publicField(this, "wsInstance", null);
@@ -120,13 +119,6 @@ class XFStreamVoiceManager {
120
119
  }
121
120
  startNewSocket() {
122
121
  var _a, _b;
123
- console.log(
124
- "%c ❤️ ==== start new socket:",
125
- "color: red; font-size: 16px;",
126
- startCount,
127
- (/* @__PURE__ */ new Date()).toLocaleString()
128
- );
129
- startCount++;
130
122
  const websocketUrl = (_b = (_a = this.config).getWebSocketUrl) == null ? void 0 : _b.call(_a);
131
123
  if (!websocketUrl) {
132
124
  this.config.onError({ type: "socketConnect", message: "获取 websocket url 失败" });
@@ -1 +1 @@
1
- {"version":3,"file":"XFStreamVoiceManager.es.js","sources":["../../../src/utils/xunFeiVoice/XFStreamVoiceManager.ts"],"sourcesContent":["import { RecorderManager } from './RecorderManager';\nimport type { OnMessageInfo, RecordFrameInfo } from './types';\nimport {\n checkMicrophonePermission,\n isWsClosed,\n requestMicrophonePermission,\n toBase64,\n} from './utils';\n\ntype errorType = 'socketConnect' | 'message' | 'record' | 'recordPermission';\nlet startCount = 0;\n\nexport interface XFStreamVoiceManagerConfig {\n onError: (errInfo: { type: errorType; message: string }) => void;\n /** ws 收到消息的回调\n * @param info.message 消息内容\n * @param info.isLatest 是否是最后一条消息。 isLatest 为 true 时, message 为完整的语音识别结果。\n */\n onMessage: (info: OnMessageInfo) => void;\n /** webSocket 关闭的回调。\n * - 每次录音都会新建一个 webSocket 连接。录音结束后关闭 webSocket\n */\n onWsClose: () => void;\n /**\n * 录音开始的回调\n */\n onRecordStart?: () => void;\n /**\n * 录音开始的回调\n */\n onRecordStop?: () => void;\n /**\n * 录音最后一帧的回调。\n * @param totalData 所有的录音数据, ArrayBuffer[]。\n */\n onLastFrame?: (totalData: ArrayBuffer[]) => void;\n /**\n * 获取链接讯飞语音识别地址的 url\n * @returns websocket url。详情查看讯飞文档里的鉴权方法:https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B\n */\n getWebSocketUrl: () => string;\n /**\n * 获取录音权限的回调, 用户初次授权或者没有给录音权限时,会调用此回调。\n * @param status 权限状态。\n * - denied: 已拒绝\n * - granted: 已授权\n */\n onGetMicrophonePermission?: (status: 'denied' | 'granted') => void;\n /**\n * 讯飞语音的 appid\n */\n xunFeiAppId: string;\n}\n\n/**\n * 讯飞流式语音识别\n * 大致流程:\n * 1. recorder start 后开始接受录音,通过 recorder.onFrameRecorded 把数据透出给上层。数据中 isLastFrame 表示是否最后一次数据, frameBuffer 表示真实数据。\n * 2. 上层拿到录音数据后,通过 ws 发送给讯飞。如果 isLastFrame 为 true 时,给讯飞的数据中 status 为 2。如果 isLastFrame 为 false 时,给讯飞的数据中 status 为 1。\n * 3. ws 返回的数据,通过 renderResult 处理。数据中 status 为 2 表示本次录音数据识别结束了,这时候需要关闭 ws,避免消耗资源。且这两个情况服务端会主动断开连接(整个会话时长最多持续60s,或者超过10s未发送数据)。\n * 讯飞流式语音文档:https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B\n * 4. 断开链接后会关闭 recorder ,重置录音等数据,方便下次使用。\n * 备注:使用时,建议在收到 onWsClose 事件后,才让前端恢复到可以再次输入录音的状态\n */\nexport class XFStreamVoiceManager {\n wsInstance: WebSocket | null = null;\n recorder: RecorderManager | null = null;\n isDestroy = false;\n resultText = '';\n resultTextTemp = '';\n continueRecord = false;\n startTimeStamp = 0;\n recordStatus: 'start' | 'stop' = 'stop';\n readyToSend = false;\n microphonePermissionStatus: 'denied' | 'granted' | 'prompt' = 'prompt';\n\n private inStopping = false;\n private stopResolve: ((value: string) => void) | null = null;\n\n constructor(public config: XFStreamVoiceManagerConfig) {\n this.initializeRecorder();\n }\n renderResult(resultData: string) {\n // 识别结束\n const jsonData = JSON.parse(resultData);\n if (jsonData.data && jsonData.data.result) {\n const data = jsonData.data.result;\n let str = '';\n const ws = data.ws;\n for (let i = 0; i < ws.length; i++) {\n str = str + ws[i].cw[0].w;\n }\n // 开启 wpgs 会有此字段(前提:在控制台开通动态修正功能)\n // 取值为 \"apd\"时表示该片结果是追加到前面的最终结果;取值为\"rpl\" 时表示替换前面的部分结果,替换范围为rg字段\n if (data.pgs) {\n if (data.pgs === 'apd') {\n // 将resultTextTemp同步给resultText\n this.resultText = this.resultTextTemp;\n }\n // 将结果存储在resultTextTemp中\n this.resultTextTemp = this.resultText + str;\n } else {\n this.resultText = this.resultText + str;\n }\n }\n /**\n * 识别结果是否结束标识:\n * 0:识别的第一块结果。\n * 1:识别中间结果。\n * 2:识别最后一块结果\n */\n if (jsonData.code === 0 && jsonData.data.status === 2) {\n this.config.onMessage({\n message: this.resultText,\n isLatest: true,\n tempMessage: this.resultTextTemp,\n });\n this.closeWs();\n return;\n }\n if (jsonData.code === 0 && jsonData.data.status === 1) {\n this.config.onMessage({\n message: this.resultText,\n isLatest: false,\n tempMessage: this.resultTextTemp,\n });\n }\n // code 不为0时表示出错\n if (jsonData.code !== 0) {\n this.closeWs();\n this.config.onError({ type: 'message', message: resultData });\n }\n }\n\n setConfig(newConfig: Partial<XFStreamVoiceManagerConfig>) {\n this.config = {\n ...this.config,\n ...newConfig,\n };\n this.recorder?.destroy();\n this.initializeRecorder();\n }\n sendMessage(message: string) {\n if (this.wsInstance?.readyState === WebSocket.OPEN && this.readyToSend) {\n this.wsInstance.send(message);\n }\n }\n\n async recordConfigSend() {\n if (this.isDestroy) {\n return;\n }\n if (this.recordStatus === 'stop') {\n this.closeWs();\n return;\n }\n try {\n // 开始录音\n await this.recorder?.start({\n sampleRate: 16000,\n frameSize: 1280,\n });\n const params = {\n common: {\n app_id: this.config.xunFeiAppId,\n },\n business: {\n language: 'zh_cn',\n domain: 'iat',\n accent: 'mandarin',\n vad_eos: 5000,\n dwa: 'wpgs',\n },\n data: {\n status: 0,\n format: 'audio/L16;rate=16000',\n encoding: 'raw',\n },\n };\n this.readyToSend = true;\n this.sendMessage(JSON.stringify(params));\n } catch (error) {\n console.error('record error:', error);\n if ((error as Error).name === 'NotAllowedError') {\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.closeWs();\n return;\n }\n this.config.onError({ type: 'record', message: '开始录音失败!' });\n }\n }\n\n startNewSocket() {\n console.log(\n '%c ❤️ ==== start new socket:',\n 'color: red; font-size: 16px;',\n startCount,\n new Date().toLocaleString(),\n );\n startCount++;\n const websocketUrl = this.config.getWebSocketUrl?.();\n if (!websocketUrl) {\n this.config.onError({ type: 'socketConnect', message: '获取 websocket url 失败' });\n return;\n }\n if ('WebSocket' in window) {\n this.wsInstance = new WebSocket(websocketUrl);\n } else {\n this.config.onError({ type: 'socketConnect', message: '浏览器不支持 WebSocket' });\n return;\n }\n this.wsInstance.onmessage = (e) => {\n // 处理返回数据\n this.renderResult(e.data);\n };\n this.wsInstance.onerror = (e) => {\n console.error('socket error:', e);\n if (this.inStopping) {\n this.inStopping = false;\n this.stopResolve?.(this.resultText);\n }\n };\n this.wsInstance.onclose = async () => {\n await this.resetStatus();\n this.config.onWsClose();\n if (this.continueRecord) {\n this.readyToSend = false;\n this.startRecord();\n }\n if (this.inStopping) {\n this.inStopping = false;\n this.stopResolve?.(this.resultText);\n }\n };\n this.wsInstance.onopen = () => {\n this.recordConfigSend();\n };\n }\n\n onFrameRecorded({ isLastFrame, frameBuffer }: RecordFrameInfo) {\n if (this.isDestroy) {\n return;\n }\n if (!this.wsInstance) {\n return;\n }\n if (this.wsInstance.readyState === this.wsInstance.OPEN) {\n this.sendMessage(\n JSON.stringify({\n data: {\n status: isLastFrame ? 2 : 1,\n format: 'audio/L16;rate=16000',\n encoding: 'raw',\n audio: toBase64(frameBuffer),\n },\n }),\n );\n }\n if (isLastFrame) {\n this.readyToSend = false;\n }\n }\n\n initializeRecorder() {\n this.recorder = new RecorderManager();\n this.recorder.onFrameRecorded = (data: RecordFrameInfo) => {\n this.onFrameRecorded(data);\n };\n this.recorder.onLastFrame = (data: ArrayBuffer[]) => {\n this.config.onLastFrame?.(data);\n };\n this.recorder.onStop = () => {\n this.config.onRecordStop?.();\n };\n this.recorder.onStart = () => {\n this.config.onRecordStart?.();\n };\n }\n\n async startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.recordStatus = 'start';\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\n if (this.microphonePermissionStatus !== 'granted') {\n const currMicrophoneStatus = await checkMicrophonePermission();\n this.microphonePermissionStatus = currMicrophoneStatus;\n if (currMicrophoneStatus === 'prompt') {\n await requestMicrophonePermission()\n .then(() => {\n this.config.onGetMicrophonePermission?.('granted');\n })\n .catch((error) => {\n console.error('requestMicrophonePermission error:', error);\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.config.onGetMicrophonePermission?.('denied');\n });\n this.stopRecord();\n return;\n }\n if (currMicrophoneStatus === 'denied') {\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.stopRecord();\n this.config.onGetMicrophonePermission?.('denied');\n return;\n }\n }\n this.startNewSocket();\n }\n\n async stopRecord() {\n this.recordStatus = 'stop';\n this.inStopping = true;\n console.log('stop record ====', Date.now() - this.startTimeStamp, this.recorder?.status);\n this.recorder?.stop();\n // 如果 recorder 还没有初始化完成就关闭了,那么需要手动关闭 ws,否则在收到最后一帧消息时关闭ws\n // if (this.recorder?.status !== 'initialized') {\n // this.closeWs();\n // }\n const stopPromise = new Promise((resolve) => {\n this.stopResolve = resolve;\n });\n await stopPromise;\n if (isWsClosed(this.wsInstance)) {\n this.stopResolve?.(this.resultText);\n }\n this.stopResolve = null;\n }\n\n closeWs() {\n if (this.wsInstance?.readyState !== WebSocket.CLOSED) {\n try {\n this.wsInstance?.close();\n } catch {\n // 有的浏览器在 ws 为 connecting 的时候 close 会报错,这里忽略掉\n }\n }\n }\n /** 开启持续监听 */\n async startContinueRecord() {\n this.continueRecord = true;\n await this.startRecord();\n }\n\n async stopContinueRecord() {\n this.continueRecord = false;\n await this.stopRecord();\n }\n\n async resetStatus() {\n this.recordStatus = 'stop';\n this.resultText = '';\n this.resultTextTemp = '';\n this.readyToSend = false;\n if (this.recorder?.audioContext?.state !== 'closed') {\n await this.recorder?.audioContext?.close();\n }\n }\n\n destroy() {\n this.isDestroy = true;\n this.closeWs();\n this.recorder?.destroy();\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
1
+ {"version":3,"file":"XFStreamVoiceManager.es.js","sources":["../../../src/utils/xunFeiVoice/XFStreamVoiceManager.ts"],"sourcesContent":["import { RecorderManager } from './RecorderManager';\nimport type { OnMessageInfo, RecordFrameInfo } from './types';\nimport {\n checkMicrophonePermission,\n isWsClosed,\n requestMicrophonePermission,\n toBase64,\n} from './utils';\n\ntype errorType = 'socketConnect' | 'message' | 'record' | 'recordPermission';\n\nexport interface XFStreamVoiceManagerConfig {\n onError: (errInfo: { type: errorType; message: string }) => void;\n /** ws 收到消息的回调\n * @param info.message 消息内容\n * @param info.isLatest 是否是最后一条消息。 isLatest 为 true 时, message 为完整的语音识别结果。\n */\n onMessage: (info: OnMessageInfo) => void;\n /** webSocket 关闭的回调。\n * - 每次录音都会新建一个 webSocket 连接。录音结束后关闭 webSocket\n */\n onWsClose: () => void;\n /**\n * 录音开始的回调\n */\n onRecordStart?: () => void;\n /**\n * 录音开始的回调\n */\n onRecordStop?: () => void;\n /**\n * 录音最后一帧的回调。\n * @param totalData 所有的录音数据, ArrayBuffer[]。\n */\n onLastFrame?: (totalData: ArrayBuffer[]) => void;\n /**\n * 获取链接讯飞语音识别地址的 url\n * @returns websocket url。详情查看讯飞文档里的鉴权方法:https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B\n */\n getWebSocketUrl: () => string;\n /**\n * 获取录音权限的回调, 用户初次授权或者没有给录音权限时,会调用此回调。\n * @param status 权限状态。\n * - denied: 已拒绝\n * - granted: 已授权\n */\n onGetMicrophonePermission?: (status: 'denied' | 'granted') => void;\n /**\n * 讯飞语音的 appid\n */\n xunFeiAppId: string;\n}\n\n/**\n * 讯飞流式语音识别\n * 大致流程:\n * 1. recorder start 后开始接受录音,通过 recorder.onFrameRecorded 把数据透出给上层。数据中 isLastFrame 表示是否最后一次数据, frameBuffer 表示真实数据。\n * 2. 上层拿到录音数据后,通过 ws 发送给讯飞。如果 isLastFrame 为 true 时,给讯飞的数据中 status 为 2。如果 isLastFrame 为 false 时,给讯飞的数据中 status 为 1。\n * 3. ws 返回的数据,通过 renderResult 处理。数据中 status 为 2 表示本次录音数据识别结束了,这时候需要关闭 ws,避免消耗资源。且这两个情况服务端会主动断开连接(整个会话时长最多持续60s,或者超过10s未发送数据)。\n * 讯飞流式语音文档:https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B\n * 4. 断开链接后会关闭 recorder ,重置录音等数据,方便下次使用。\n * 备注:使用时,建议在收到 onWsClose 事件后,才让前端恢复到可以再次输入录音的状态\n */\nexport class XFStreamVoiceManager {\n wsInstance: WebSocket | null = null;\n recorder: RecorderManager | null = null;\n isDestroy = false;\n resultText = '';\n resultTextTemp = '';\n continueRecord = false;\n startTimeStamp = 0;\n recordStatus: 'start' | 'stop' = 'stop';\n readyToSend = false;\n microphonePermissionStatus: 'denied' | 'granted' | 'prompt' = 'prompt';\n\n private inStopping = false;\n private stopResolve: ((value: string) => void) | null = null;\n\n constructor(public config: XFStreamVoiceManagerConfig) {\n this.initializeRecorder();\n }\n renderResult(resultData: string) {\n // 识别结束\n const jsonData = JSON.parse(resultData);\n if (jsonData.data && jsonData.data.result) {\n const data = jsonData.data.result;\n let str = '';\n const ws = data.ws;\n for (let i = 0; i < ws.length; i++) {\n str = str + ws[i].cw[0].w;\n }\n // 开启 wpgs 会有此字段(前提:在控制台开通动态修正功能)\n // 取值为 \"apd\"时表示该片结果是追加到前面的最终结果;取值为\"rpl\" 时表示替换前面的部分结果,替换范围为rg字段\n if (data.pgs) {\n if (data.pgs === 'apd') {\n // 将resultTextTemp同步给resultText\n this.resultText = this.resultTextTemp;\n }\n // 将结果存储在resultTextTemp中\n this.resultTextTemp = this.resultText + str;\n } else {\n this.resultText = this.resultText + str;\n }\n }\n /**\n * 识别结果是否结束标识:\n * 0:识别的第一块结果。\n * 1:识别中间结果。\n * 2:识别最后一块结果\n */\n if (jsonData.code === 0 && jsonData.data.status === 2) {\n this.config.onMessage({\n message: this.resultText,\n isLatest: true,\n tempMessage: this.resultTextTemp,\n });\n this.closeWs();\n return;\n }\n if (jsonData.code === 0 && jsonData.data.status === 1) {\n this.config.onMessage({\n message: this.resultText,\n isLatest: false,\n tempMessage: this.resultTextTemp,\n });\n }\n // code 不为0时表示出错\n if (jsonData.code !== 0) {\n this.closeWs();\n this.config.onError({ type: 'message', message: resultData });\n }\n }\n\n setConfig(newConfig: Partial<XFStreamVoiceManagerConfig>) {\n this.config = {\n ...this.config,\n ...newConfig,\n };\n this.recorder?.destroy();\n this.initializeRecorder();\n }\n sendMessage(message: string) {\n if (this.wsInstance?.readyState === WebSocket.OPEN && this.readyToSend) {\n this.wsInstance.send(message);\n }\n }\n\n async recordConfigSend() {\n if (this.isDestroy) {\n return;\n }\n if (this.recordStatus === 'stop') {\n this.closeWs();\n return;\n }\n try {\n // 开始录音\n await this.recorder?.start({\n sampleRate: 16000,\n frameSize: 1280,\n });\n const params = {\n common: {\n app_id: this.config.xunFeiAppId,\n },\n business: {\n language: 'zh_cn',\n domain: 'iat',\n accent: 'mandarin',\n vad_eos: 5000,\n dwa: 'wpgs',\n },\n data: {\n status: 0,\n format: 'audio/L16;rate=16000',\n encoding: 'raw',\n },\n };\n this.readyToSend = true;\n this.sendMessage(JSON.stringify(params));\n } catch (error) {\n console.error('record error:', error);\n if ((error as Error).name === 'NotAllowedError') {\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.closeWs();\n return;\n }\n this.config.onError({ type: 'record', message: '开始录音失败!' });\n }\n }\n\n startNewSocket() {\n const websocketUrl = this.config.getWebSocketUrl?.();\n if (!websocketUrl) {\n this.config.onError({ type: 'socketConnect', message: '获取 websocket url 失败' });\n return;\n }\n if ('WebSocket' in window) {\n this.wsInstance = new WebSocket(websocketUrl);\n } else {\n this.config.onError({ type: 'socketConnect', message: '浏览器不支持 WebSocket' });\n return;\n }\n this.wsInstance.onmessage = (e) => {\n // 处理返回数据\n this.renderResult(e.data);\n };\n this.wsInstance.onerror = (e) => {\n console.error('socket error:', e);\n if (this.inStopping) {\n this.inStopping = false;\n this.stopResolve?.(this.resultText);\n }\n };\n this.wsInstance.onclose = async () => {\n await this.resetStatus();\n this.config.onWsClose();\n if (this.continueRecord) {\n this.readyToSend = false;\n this.startRecord();\n }\n if (this.inStopping) {\n this.inStopping = false;\n this.stopResolve?.(this.resultText);\n }\n };\n this.wsInstance.onopen = () => {\n this.recordConfigSend();\n };\n }\n\n onFrameRecorded({ isLastFrame, frameBuffer }: RecordFrameInfo) {\n if (this.isDestroy) {\n return;\n }\n if (!this.wsInstance) {\n return;\n }\n if (this.wsInstance.readyState === this.wsInstance.OPEN) {\n this.sendMessage(\n JSON.stringify({\n data: {\n status: isLastFrame ? 2 : 1,\n format: 'audio/L16;rate=16000',\n encoding: 'raw',\n audio: toBase64(frameBuffer),\n },\n }),\n );\n }\n if (isLastFrame) {\n this.readyToSend = false;\n }\n }\n\n initializeRecorder() {\n this.recorder = new RecorderManager();\n this.recorder.onFrameRecorded = (data: RecordFrameInfo) => {\n this.onFrameRecorded(data);\n };\n this.recorder.onLastFrame = (data: ArrayBuffer[]) => {\n this.config.onLastFrame?.(data);\n };\n this.recorder.onStop = () => {\n this.config.onRecordStop?.();\n };\n this.recorder.onStart = () => {\n this.config.onRecordStart?.();\n };\n }\n\n async startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.recordStatus = 'start';\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\n if (this.microphonePermissionStatus !== 'granted') {\n const currMicrophoneStatus = await checkMicrophonePermission();\n this.microphonePermissionStatus = currMicrophoneStatus;\n if (currMicrophoneStatus === 'prompt') {\n await requestMicrophonePermission()\n .then(() => {\n this.config.onGetMicrophonePermission?.('granted');\n })\n .catch((error) => {\n console.error('requestMicrophonePermission error:', error);\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.config.onGetMicrophonePermission?.('denied');\n });\n this.stopRecord();\n return;\n }\n if (currMicrophoneStatus === 'denied') {\n this.config.onError({ type: 'recordPermission', message: '没有录音权限' });\n this.stopRecord();\n this.config.onGetMicrophonePermission?.('denied');\n return;\n }\n }\n this.startNewSocket();\n }\n\n async stopRecord() {\n this.recordStatus = 'stop';\n this.inStopping = true;\n console.log('stop record ====', Date.now() - this.startTimeStamp, this.recorder?.status);\n this.recorder?.stop();\n // 如果 recorder 还没有初始化完成就关闭了,那么需要手动关闭 ws,否则在收到最后一帧消息时关闭ws\n // if (this.recorder?.status !== 'initialized') {\n // this.closeWs();\n // }\n const stopPromise = new Promise((resolve) => {\n this.stopResolve = resolve;\n });\n await stopPromise;\n if (isWsClosed(this.wsInstance)) {\n this.stopResolve?.(this.resultText);\n }\n this.stopResolve = null;\n }\n\n closeWs() {\n if (this.wsInstance?.readyState !== WebSocket.CLOSED) {\n try {\n this.wsInstance?.close();\n } catch {\n // 有的浏览器在 ws 为 connecting 的时候 close 会报错,这里忽略掉\n }\n }\n }\n /** 开启持续监听 */\n async startContinueRecord() {\n this.continueRecord = true;\n await this.startRecord();\n }\n\n async stopContinueRecord() {\n this.continueRecord = false;\n await this.stopRecord();\n }\n\n async resetStatus() {\n this.recordStatus = 'stop';\n this.resultText = '';\n this.resultTextTemp = '';\n this.readyToSend = false;\n if (this.recorder?.audioContext?.state !== 'closed') {\n await this.recorder?.audioContext?.close();\n }\n }\n\n destroy() {\n this.isDestroy = true;\n this.closeWs();\n this.recorder?.destroy();\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easyv/biz-components",
3
- "version": "0.0.50",
3
+ "version": "0.0.51",
4
4
  "type": "module",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",