@easyv/biz-components 0.0.21 → 0.0.22

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.
@@ -60,6 +60,8 @@ export declare class XFStreamVoiceManager {
60
60
  startTimeStamp: number;
61
61
  recordStatus: 'start' | 'stop';
62
62
  readyToSend: boolean;
63
+ private inStopping;
64
+ private stopResolve;
63
65
  constructor(config: XFStreamVoiceManagerConfig);
64
66
  renderResult(resultData: string): void;
65
67
  setConfig(newConfig: XFStreamVoiceManagerConfig): void;
@@ -72,8 +74,8 @@ export declare class XFStreamVoiceManager {
72
74
  stopRecord(): Promise<void>;
73
75
  closeWs(): void;
74
76
  /** 开启持续监听 */
75
- startContinueRecord(): void;
76
- stopContinueRecord(): void;
77
+ startContinueRecord(): Promise<void>;
78
+ stopContinueRecord(): Promise<void>;
77
79
  destroy(): void;
78
80
  }
79
81
  export {};
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import CryptoJS from "../../node_modules/.pnpm/crypto-js@4.2.0/node_modules/crypto-js/index.es.js";
5
- import { toBase64 } from "./utils.es.js";
5
+ import { toBase64, isWsClosed } from "./utils.es.js";
6
6
  import { RecorderManager } from "./RecorderManager/RecorderManager.es.js";
7
7
  let startCount = 0;
8
8
  const APPID = "93b73e33";
@@ -36,6 +36,8 @@ class XFStreamVoiceManager {
36
36
  __publicField(this, "startTimeStamp", 0);
37
37
  __publicField(this, "recordStatus", "stop");
38
38
  __publicField(this, "readyToSend", false);
39
+ __publicField(this, "inStopping", false);
40
+ __publicField(this, "stopResolve", null);
39
41
  this.config = config;
40
42
  this.initializeRecorder();
41
43
  }
@@ -151,16 +153,25 @@ class XFStreamVoiceManager {
151
153
  this.renderResult(e.data);
152
154
  };
153
155
  this.wsInstance.onerror = (e) => {
156
+ var _a2;
154
157
  console.error("socket error:", e);
158
+ if (this.inStopping) {
159
+ this.inStopping = false;
160
+ (_a2 = this.stopResolve) == null ? void 0 : _a2.call(this, this.resultText);
161
+ }
155
162
  };
156
163
  this.wsInstance.onclose = async () => {
157
- var _a2;
164
+ var _a2, _b2;
158
165
  this.config.onWsClose();
159
166
  if (this.continueRecord) {
160
167
  this.readyToSend = false;
161
168
  await ((_a2 = this.recorder) == null ? void 0 : _a2.close());
162
169
  this.startRecord();
163
170
  }
171
+ if (this.inStopping) {
172
+ this.inStopping = false;
173
+ (_b2 = this.stopResolve) == null ? void 0 : _b2.call(this, this.resultText);
174
+ }
164
175
  };
165
176
  this.wsInstance.onopen = () => {
166
177
  this.recordConfigSend();
@@ -218,13 +229,22 @@ class XFStreamVoiceManager {
218
229
  this.startNewSocket();
219
230
  }
220
231
  async stopRecord() {
221
- var _a, _b;
232
+ var _a, _b, _c;
222
233
  this.recordStatus = "stop";
234
+ this.inStopping = true;
223
235
  console.log("stop record ====", Date.now() - this.startTimeStamp, (_a = this.recorder) == null ? void 0 : _a.status);
224
236
  (_b = this.recorder) == null ? void 0 : _b.stop();
237
+ const stopPromise = new Promise((resolve) => {
238
+ this.stopResolve = resolve;
239
+ });
225
240
  setTimeout(() => {
226
241
  this.closeWs();
227
242
  }, 3e3);
243
+ await stopPromise;
244
+ if (isWsClosed(this.wsInstance)) {
245
+ (_c = this.stopResolve) == null ? void 0 : _c.call(this, this.resultText);
246
+ }
247
+ this.stopResolve = null;
228
248
  }
229
249
  closeWs() {
230
250
  var _a, _b;
@@ -236,11 +256,11 @@ class XFStreamVoiceManager {
236
256
  }
237
257
  }
238
258
  /** 开启持续监听 */
239
- startContinueRecord() {
259
+ async startContinueRecord() {
240
260
  this.continueRecord = true;
241
- this.startRecord();
261
+ await this.startRecord();
242
262
  }
243
- stopContinueRecord() {
263
+ async stopContinueRecord() {
244
264
  this.continueRecord = false;
245
265
  this.stopRecord();
246
266
  }
@@ -1 +1 @@
1
- {"version":3,"file":"XFStreamVoiceManager.es.js","sources":["../../../src/utils/xunFeiVoice/XFStreamVoiceManager.ts"],"sourcesContent":["import CryptoJS from 'crypto-js';\nimport { RecorderManager } from './RecorderManager';\nimport type { OnMessageInfo, RecordFrameInfo } from './types';\nimport { toBase64 } from './utils';\n\ntype errorType = 'socketConnect' | 'message' | 'record';\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 * 讯飞语音的 appid\n */\n xunFeiAppId: string;\n}\n\n/** 现在前端mock,后续移到接口下发 */\nconst APPID = '93b73e33';\nconst API_SECRET = 'ZGJhMzQ5ZTJlMDgyYmE1ZTFlZDlmYjg0';\nconst API_KEY = 'fe43de7a071e3a32ec03c6f09fe7bedf';\n/**\n * 获取websocket url\n * 该接口需要后端提供,这里为了方便前端处理\n */\nfunction getWebSocketUrl() {\n // 请求地址根据语种不同变化\n const url = 'wss://iat-api.xfyun.cn/v2/iat';\n const host = 'iat-api.xfyun.cn';\n const apiKey = API_KEY;\n const apiSecret = API_SECRET;\n const date = new Date().toUTCString();\n const algorithm = 'hmac-sha256';\n const headers = 'host date request-line';\n const signatureOrigin = `host: ${host}\\ndate: ${date}\\nGET /v2/iat HTTP/1.1`;\n const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);\n const signature = CryptoJS.enc.Base64.stringify(signatureSha);\n const authorizationOrigin = `api_key=\"${apiKey}\", algorithm=\"${algorithm}\", headers=\"${headers}\", signature=\"${signature}\"`;\n const authorization = window.btoa(authorizationOrigin);\n return `${url}?authorization=${authorization}&date=${date}&host=${host}`;\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\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: XFStreamVoiceManagerConfig) {\n this.config = {\n ...this.config,\n ...newConfig,\n };\n this.recorder?.close();\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 || APPID,\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 this.config.onError({ type: 'record', message: '录音失败, 请检查权限!' });\n }\n }\n\n startNewSocket() {\n console.log(\n '%c ❤️ love ==== start new socket:',\n 'color: red; font-size: 16px;',\n startCount,\n new Date().toLocaleString(),\n );\n startCount++;\n const websocketUrl = this.config.getWebSocketUrl?.() || getWebSocketUrl();\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 };\n this.wsInstance.onclose = async () => {\n this.config.onWsClose();\n if (this.continueRecord) {\n this.readyToSend = false;\n await this.recorder?.close();\n this.startRecord();\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 startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.recordStatus = 'start';\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\n this.startNewSocket();\n }\n\n async stopRecord() {\n this.recordStatus = 'stop';\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 // 这里为了防止 ws 没有返回结束的数据,导致 ws 一直没关,所以加个定时器,5s 后手动关闭\n setTimeout(() => {\n this.closeWs();\n }, 3000);\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 startContinueRecord() {\n this.continueRecord = true;\n this.startRecord();\n }\n\n stopContinueRecord() {\n this.continueRecord = false;\n this.stopRecord();\n }\n\n destroy() {\n this.isDestroy = true;\n setTimeout(() => {\n this.closeWs();\n this.recorder?.stop();\n this.recorder?.destroy();\n }, 100);\n }\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"XFStreamVoiceManager.es.js","sources":["../../../src/utils/xunFeiVoice/XFStreamVoiceManager.ts"],"sourcesContent":["import CryptoJS from 'crypto-js';\nimport { RecorderManager } from './RecorderManager';\nimport type { OnMessageInfo, RecordFrameInfo } from './types';\nimport { isWsClosed, toBase64 } from './utils';\n\ntype errorType = 'socketConnect' | 'message' | 'record';\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 * 讯飞语音的 appid\n */\n xunFeiAppId: string;\n}\n\n/** 现在前端mock,后续移到接口下发 */\nconst APPID = '93b73e33';\nconst API_SECRET = 'ZGJhMzQ5ZTJlMDgyYmE1ZTFlZDlmYjg0';\nconst API_KEY = 'fe43de7a071e3a32ec03c6f09fe7bedf';\n/**\n * 获取websocket url\n * 该接口需要后端提供,这里为了方便前端处理\n */\nfunction getWebSocketUrl() {\n // 请求地址根据语种不同变化\n const url = 'wss://iat-api.xfyun.cn/v2/iat';\n const host = 'iat-api.xfyun.cn';\n const apiKey = API_KEY;\n const apiSecret = API_SECRET;\n const date = new Date().toUTCString();\n const algorithm = 'hmac-sha256';\n const headers = 'host date request-line';\n const signatureOrigin = `host: ${host}\\ndate: ${date}\\nGET /v2/iat HTTP/1.1`;\n const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);\n const signature = CryptoJS.enc.Base64.stringify(signatureSha);\n const authorizationOrigin = `api_key=\"${apiKey}\", algorithm=\"${algorithm}\", headers=\"${headers}\", signature=\"${signature}\"`;\n const authorization = window.btoa(authorizationOrigin);\n return `${url}?authorization=${authorization}&date=${date}&host=${host}`;\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\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: XFStreamVoiceManagerConfig) {\n this.config = {\n ...this.config,\n ...newConfig,\n };\n this.recorder?.close();\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 || APPID,\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 this.config.onError({ type: 'record', message: '录音失败, 请检查权限!' });\n }\n }\n\n startNewSocket() {\n console.log(\n '%c ❤️ love ==== start new socket:',\n 'color: red; font-size: 16px;',\n startCount,\n new Date().toLocaleString(),\n );\n startCount++;\n const websocketUrl = this.config.getWebSocketUrl?.() || getWebSocketUrl();\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 this.config.onWsClose();\n if (this.continueRecord) {\n this.readyToSend = false;\n await this.recorder?.close();\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 startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.recordStatus = 'start';\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\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 // 这里为了防止 ws 没有返回结束的数据,导致 ws 一直没关,所以加个定时器,5s 后手动关闭\n const stopPromise = new Promise((resolve) => {\n this.stopResolve = resolve;\n });\n setTimeout(() => {\n this.closeWs();\n }, 3000);\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 this.stopRecord();\n }\n\n destroy() {\n this.isDestroy = true;\n setTimeout(() => {\n this.closeWs();\n this.recorder?.stop();\n this.recorder?.destroy();\n }, 100);\n }\n}\n"],"names":["_a","_b"],"mappings":"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"}
@@ -1 +1,2 @@
1
1
  export declare function toBase64(buffer: ArrayBuffer): string;
2
+ export declare function isWsClosed(ws?: WebSocket | null): boolean;
@@ -7,7 +7,14 @@ function toBase64(buffer) {
7
7
  }
8
8
  return window.btoa(binary);
9
9
  }
10
+ function isWsClosed(ws) {
11
+ if (!ws) {
12
+ return true;
13
+ }
14
+ return ws.readyState === WebSocket.CLOSED;
15
+ }
10
16
  export {
17
+ isWsClosed,
11
18
  toBase64
12
19
  };
13
20
  //# sourceMappingURL=utils.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.es.js","sources":["../../../src/utils/xunFeiVoice/utils.ts"],"sourcesContent":["export function toBase64(buffer: ArrayBuffer) {\n let binary = '';\n const bytes = new Uint8Array(buffer);\n const len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return window.btoa(binary);\n}\n"],"names":[],"mappings":"AAAO,SAAS,SAAS,QAAqB;AAC5C,MAAI,SAAS;AACP,QAAA,QAAQ,IAAI,WAAW,MAAM;AACnC,QAAM,MAAM,MAAM;AAClB,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EAAA;AAEjC,SAAA,OAAO,KAAK,MAAM;AAC3B;"}
1
+ {"version":3,"file":"utils.es.js","sources":["../../../src/utils/xunFeiVoice/utils.ts"],"sourcesContent":["export function toBase64(buffer: ArrayBuffer) {\n let binary = '';\n const bytes = new Uint8Array(buffer);\n const len = bytes.byteLength;\n for (let i = 0; i < len; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return window.btoa(binary);\n}\n\nexport function isWsClosed(ws?: WebSocket | null) {\n if (!ws) {\n return true;\n }\n return ws.readyState === WebSocket.CLOSED;\n}\n"],"names":[],"mappings":"AAAO,SAAS,SAAS,QAAqB;AAC5C,MAAI,SAAS;AACP,QAAA,QAAQ,IAAI,WAAW,MAAM;AACnC,QAAM,MAAM,MAAM;AAClB,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EAAA;AAEjC,SAAA,OAAO,KAAK,MAAM;AAC3B;AAEO,SAAS,WAAW,IAAuB;AAChD,MAAI,CAAC,IAAI;AACA,WAAA;AAAA,EAAA;AAEF,SAAA,GAAG,eAAe,UAAU;AACrC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easyv/biz-components",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",