@easyv/biz-components 0.0.18 → 0.0.19
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/hooks/useXunFeiSteamVoiceManager.d.ts +2 -2
- package/dist/hooks/useXunFeiSteamVoiceManager.es.js +4 -4
- package/dist/hooks/useXunFeiSteamVoiceManager.es.js.map +1 -1
- package/dist/index.es.js +12 -12
- package/dist/utils/xunFeiVoice/{XunFeiStreamVoiceManager.d.ts → XFStreamVoiceManager.d.ts} +5 -5
- package/dist/utils/xunFeiVoice/{XunFeiStreamVoiceManager.es.js → XFStreamVoiceManager.es.js} +2 -2
- package/dist/utils/xunFeiVoice/XFStreamVoiceManager.es.js.map +1 -0
- package/dist/utils/xunFeiVoice/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/utils/xunFeiVoice/XunFeiStreamVoiceManager.es.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { XFStreamVoiceManager, XFStreamVoiceManagerConfig } from '../utils';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* 讯飞流式语音识别的 hook,注意 config 只在初识化时注册一次,后续更改,请使用 manager.setConfig 方法更新
|
|
5
5
|
* @param config XunFeiStreamVoiceManagerConfig
|
|
6
6
|
* @returns
|
|
7
7
|
*/
|
|
8
|
-
export declare const useXunFeiSteamVoiceManager: (config:
|
|
8
|
+
export declare const useXunFeiSteamVoiceManager: (config: XFStreamVoiceManagerConfig) => XFStreamVoiceManager | null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useState as
|
|
2
|
-
import {
|
|
1
|
+
import { useState as n, useEffect as a } from "react";
|
|
2
|
+
import { XFStreamVoiceManager as s } from "../utils/xunFeiVoice/XFStreamVoiceManager.es.js";
|
|
3
3
|
const c = (t) => {
|
|
4
|
-
const [r,
|
|
4
|
+
const [r, o] = n(null);
|
|
5
5
|
return a(() => {
|
|
6
6
|
const e = new s(t);
|
|
7
|
-
return
|
|
7
|
+
return o(e), () => {
|
|
8
8
|
e == null || e.destroy();
|
|
9
9
|
};
|
|
10
10
|
}, []), r;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useXunFeiSteamVoiceManager.es.js","sources":["../../src/hooks/useXunFeiSteamVoiceManager.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport {
|
|
1
|
+
{"version":3,"file":"useXunFeiSteamVoiceManager.es.js","sources":["../../src/hooks/useXunFeiSteamVoiceManager.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { XFStreamVoiceManager, XFStreamVoiceManagerConfig } from '@/utils';\n\n/**\n * 讯飞流式语音识别的 hook,注意 config 只在初识化时注册一次,后续更改,请使用 manager.setConfig 方法更新\n * @param config XunFeiStreamVoiceManagerConfig\n * @returns\n */\nexport const useXunFeiSteamVoiceManager = (config: XFStreamVoiceManagerConfig) => {\n const [manager, setManager] = useState<XFStreamVoiceManager | null>(null);\n\n useEffect(() => {\n const newManager = new XFStreamVoiceManager(config);\n setManager(newManager);\n return () => {\n newManager?.destroy();\n };\n }, []);\n return manager;\n};\n"],"names":["useXunFeiSteamVoiceManager","config","manager","setManager","useState","useEffect","newManager","XFStreamVoiceManager"],"mappings":";;AAQa,MAAAA,IAA6B,CAACC,MAAuC;AAChF,QAAM,CAACC,GAASC,CAAU,IAAIC,EAAsC,IAAI;AAExE,SAAAC,EAAU,MAAM;AACR,UAAAC,IAAa,IAAIC,EAAqBN,CAAM;AAClD,WAAAE,EAAWG,CAAU,GACd,MAAM;AACX,MAAAA,KAAA,QAAAA,EAAY;AAAA,IACd;AAAA,EACF,GAAG,EAAE,GACEJ;AACT;"}
|
package/dist/index.es.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/* empty css */
|
|
2
2
|
import { AiMessageRender as t } from "./components/AiMessageRender/AiMessageRender.es.js";
|
|
3
|
-
import { ShadowDom as
|
|
4
|
-
import { ScrollController as
|
|
5
|
-
import { VoiceAnimation as
|
|
3
|
+
import { ShadowDom as a } from "./components/ShadowDom/ShadowDom.es.js";
|
|
4
|
+
import { ScrollController as p } from "./components/ScrollController/ScrollController.es.js";
|
|
5
|
+
import { VoiceAnimation as i } from "./components/VoiceAnimation/VoiceAnimation.es.js";
|
|
6
6
|
import { useDivAutoScroll as c } from "./hooks/useDivAutoScroll.es.js";
|
|
7
|
-
import { useXunFeiSteamVoiceManager as
|
|
8
|
-
import { FunASRManager as
|
|
9
|
-
import {
|
|
7
|
+
import { useXunFeiSteamVoiceManager as g } from "./hooks/useXunFeiSteamVoiceManager.es.js";
|
|
8
|
+
import { FunASRManager as u, getWebSocketConnectForFunASR as A } from "./utils/funASR/funASRManager.es.js";
|
|
9
|
+
import { XFStreamVoiceManager as M } from "./utils/xunFeiVoice/XFStreamVoiceManager.es.js";
|
|
10
10
|
import { RecorderManager as R } from "./utils/xunFeiVoice/RecorderManager/RecorderManager.es.js";
|
|
11
11
|
export {
|
|
12
12
|
t as AiMessageRender,
|
|
13
|
-
|
|
13
|
+
u as FunASRManager,
|
|
14
14
|
R as RecorderManager,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
M as
|
|
15
|
+
p as ScrollController,
|
|
16
|
+
a as ShadowDom,
|
|
17
|
+
i as VoiceAnimation,
|
|
18
|
+
M as XFStreamVoiceManager,
|
|
19
19
|
A as getWebSocketConnectForFunASR,
|
|
20
20
|
c as useDivAutoScroll,
|
|
21
|
-
|
|
21
|
+
g as useXunFeiSteamVoiceManager
|
|
22
22
|
};
|
|
23
23
|
//# sourceMappingURL=index.es.js.map
|
|
@@ -2,7 +2,7 @@ import { RecorderManager } from './RecorderManager';
|
|
|
2
2
|
import { OnMessageInfo, RecordFrameInfo } from './types';
|
|
3
3
|
|
|
4
4
|
type errorType = 'socketConnect' | 'message' | 'record';
|
|
5
|
-
export interface
|
|
5
|
+
export interface XFStreamVoiceManagerConfig {
|
|
6
6
|
onError: (errInfo: {
|
|
7
7
|
type: errorType;
|
|
8
8
|
message: string;
|
|
@@ -34,8 +34,8 @@ type SocketStatus = 'UNDEFINED' | 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED';
|
|
|
34
34
|
/**
|
|
35
35
|
* 讯飞流式语音识别
|
|
36
36
|
*/
|
|
37
|
-
export declare class
|
|
38
|
-
config:
|
|
37
|
+
export declare class XFStreamVoiceManager {
|
|
38
|
+
config: XFStreamVoiceManagerConfig;
|
|
39
39
|
wsInstance: WebSocket | null;
|
|
40
40
|
status: SocketStatus;
|
|
41
41
|
recorder: RecorderManager | null;
|
|
@@ -46,9 +46,9 @@ export declare class XunFeiStreamVoiceManager {
|
|
|
46
46
|
startTimeStamp: number;
|
|
47
47
|
recordStatus: 'start' | 'stop';
|
|
48
48
|
readyToSend: boolean;
|
|
49
|
-
constructor(config:
|
|
49
|
+
constructor(config: XFStreamVoiceManagerConfig);
|
|
50
50
|
renderResult(resultData: string): void;
|
|
51
|
-
setConfig(newConfig:
|
|
51
|
+
setConfig(newConfig: XFStreamVoiceManagerConfig): void;
|
|
52
52
|
sendMessage(message: string): void;
|
|
53
53
|
recordConfigSend(): Promise<void>;
|
|
54
54
|
startNewSocket(): void;
|
|
@@ -0,0 +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?: (data: 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}\ntype SocketStatus = 'UNDEFINED' | 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED';\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 */\nexport class XFStreamVoiceManager {\n wsInstance: WebSocket | null = null;\n status: SocketStatus = 'UNDEFINED';\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.wsInstance?.close();\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.wsInstance?.close();\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.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 try {\n // 开始录音\n await this.recorder?.start({\n sampleRate: 16000,\n frameSize: 1280,\n });\n const params = {\n common: {\n app_id: 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().toUTCString(),\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 this.changeBtnStatus('CLOSED');\n };\n this.wsInstance.onclose = async () => {\n this.changeBtnStatus('CLOSED');\n this.config.onWsClose();\n this.recorder?.stop();\n if (this.continueRecord) {\n this.readyToSend = false;\n await this.recorder?.close();\n this.startRecord();\n }\n };\n this.wsInstance.onopen = () => {\n this.changeBtnStatus('OPEN');\n this.recordConfigSend();\n };\n }\n\n recordProcess({ 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 this.changeBtnStatus('CLOSING');\n }\n }\n\n initializeRecorder() {\n this.recorder = new RecorderManager();\n if (!this.recorder) {\n return;\n }\n this.recorder.onFrameRecorded = (data: RecordFrameInfo) => {\n this.recordProcess(data);\n };\n this.recorder.onStop = (data: ArrayBuffer[]) => {\n this.config.onRecordStop?.(data);\n };\n this.recorder.onStart = () => {\n console.log('%c ❤️ love ==== recorder onStart:', 'color: red; font-size: 16px;');\n this.config.onRecordStart?.();\n this.changeBtnStatus('OPEN');\n };\n }\n\n changeBtnStatus(status: SocketStatus) {\n this.status = status;\n }\n\n startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\n this.startNewSocket();\n }\n\n async stopRecord() {\n console.log('stop record ====', Date.now() - this.startTimeStamp);\n // 结束录音,为了防止刚开始就关闭\n if (Date.now() - this.startTimeStamp < 500) {\n setTimeout(() => {\n this.recorder?.stop();\n }, 100);\n } else {\n this.recorder?.stop();\n }\n // 这里为了防止ws没有返回结束的数据,导致 ws 一直没关,所以加个定时器,5s 后手动关闭\n setTimeout(() => {\n if (this.wsInstance?.readyState === WebSocket.OPEN) {\n this.wsInstance?.close();\n }\n }, 5000);\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 if (this.wsInstance?.readyState === WebSocket.OPEN) {\n this.wsInstance?.close();\n }\n this.recorder?.stop();\n }, 1000);\n }\n}\n"],"names":["startCount","APPID","API_SECRET","API_KEY","getWebSocketUrl","url","host","apiKey","apiSecret","date","algorithm","headers","signatureOrigin","signatureSha","CryptoJS","signature","authorizationOrigin","authorization","XFStreamVoiceManager","config","__publicField","resultData","jsonData","data","str","ws","i","_a","_b","newConfig","message","params","error","websocketUrl","e","isLastFrame","frameBuffer","toBase64","RecorderManager","status","_c"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"XunFeiStreamVoiceManager.es.js","sources":["../../../src/utils/xunFeiVoice/XunFeiStreamVoiceManager.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 XunFeiStreamVoiceManagerConfig {\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?: (data: 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}\ntype SocketStatus = 'UNDEFINED' | 'CONNECTING' | 'OPEN' | 'CLOSING' | 'CLOSED';\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 */\nexport class XunFeiStreamVoiceManager {\n wsInstance: WebSocket | null = null;\n status: SocketStatus = 'UNDEFINED';\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: XunFeiStreamVoiceManagerConfig) {\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.wsInstance?.close();\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.wsInstance?.close();\n this.config.onError({ type: 'message', message: resultData });\n }\n }\n\n setConfig(newConfig: XunFeiStreamVoiceManagerConfig) {\n this.config = {\n ...this.config,\n ...newConfig,\n };\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 try {\n // 开始录音\n await this.recorder?.start({\n sampleRate: 16000,\n frameSize: 1280,\n });\n const params = {\n common: {\n app_id: 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().toUTCString(),\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 this.changeBtnStatus('CLOSED');\n };\n this.wsInstance.onclose = async () => {\n this.changeBtnStatus('CLOSED');\n this.config.onWsClose();\n this.recorder?.stop();\n if (this.continueRecord) {\n this.readyToSend = false;\n await this.recorder?.close();\n this.startRecord();\n }\n };\n this.wsInstance.onopen = () => {\n this.changeBtnStatus('OPEN');\n this.recordConfigSend();\n };\n }\n\n recordProcess({ 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 this.changeBtnStatus('CLOSING');\n }\n }\n\n initializeRecorder() {\n this.recorder = new RecorderManager();\n if (!this.recorder) {\n return;\n }\n this.recorder.onFrameRecorded = (data: RecordFrameInfo) => {\n this.recordProcess(data);\n };\n this.recorder.onStop = (data: ArrayBuffer[]) => {\n this.config.onRecordStop?.(data);\n };\n this.recorder.onStart = () => {\n console.log('%c ❤️ love ==== recorder onStart:', 'color: red; font-size: 16px;');\n this.config.onRecordStart?.();\n this.changeBtnStatus('OPEN');\n };\n }\n\n changeBtnStatus(status: SocketStatus) {\n this.status = status;\n }\n\n startRecord() {\n if (this.isDestroy) {\n return;\n }\n this.startTimeStamp = Date.now();\n this.resultText = '';\n this.resultTextTemp = '';\n this.startNewSocket();\n }\n\n async stopRecord() {\n console.log('stop record ====', Date.now() - this.startTimeStamp);\n // 结束录音,为了防止刚开始就关闭\n if (Date.now() - this.startTimeStamp < 500) {\n setTimeout(() => {\n this.recorder?.stop();\n }, 100);\n } else {\n this.recorder?.stop();\n }\n // 这里为了防止ws没有返回结束的数据,导致 ws 一直没关,所以加个定时器,5s 后手动关闭\n setTimeout(() => {\n if (this.wsInstance?.readyState === WebSocket.OPEN) {\n this.wsInstance?.close();\n }\n }, 5000);\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 if (this.wsInstance?.readyState === WebSocket.OPEN) {\n this.wsInstance?.close();\n }\n this.recorder?.stop();\n }, 1000);\n }\n}\n"],"names":["startCount","APPID","API_SECRET","API_KEY","getWebSocketUrl","url","host","apiKey","apiSecret","date","algorithm","headers","signatureOrigin","signatureSha","CryptoJS","signature","authorizationOrigin","authorization","XunFeiStreamVoiceManager","config","__publicField","resultData","jsonData","data","str","ws","i","_a","_b","newConfig","message","params","error","websocketUrl","e","isLastFrame","frameBuffer","toBase64","RecorderManager","status","_c"],"mappings":"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"}
|