Refactor ASR module and remove deprecated AliAsrServer

- Introduced AsrRelatime class for real-time ASR using WebSocket.
- Removed AliAsrServer and related files from the aliyun provider.
- Updated base class for ASR to use WSServer for WebSocket connections.
- Added new test cases for the updated ASR functionality.
- Cleaned up unused imports and files across the project.
- Adjusted TypeScript configuration for better module resolution.
- Implemented silence generation for audio streaming.
This commit is contained in:
2025-12-21 18:56:32 +08:00
parent 9e94a4d898
commit 58b27b86fe
20 changed files with 858 additions and 3626 deletions

View File

@@ -31,48 +31,32 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@gradio/client": "^1.15.1", "@gradio/client": "^2.0.1",
"@kevisual/router": "0.0.21", "@kevisual/router": "0.0.48",
"@kevisual/use-config": "^1.0.17", "@kevisual/use-config": "^1.0.21",
"@kevisual/video": "^0.0.2", "@kevisual/video": "^0.0.2",
"cookie": "^1.0.2",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.19",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"formidable": "^3.5.4", "nanoid": "^5.1.6"
"lodash-es": "^4.17.21",
"nanoid": "^5.1.5"
}, },
"devDependencies": { "devDependencies": {
"@alicloud/pop-core": "^1.8.0", "@alicloud/pop-core": "^1.8.0",
"@kevisual/logger": "^0.0.4", "@kevisual/logger": "^0.0.4",
"@kevisual/types": "^0.0.10", "@kevisual/types": "^0.0.10",
"@kevisual/use-config": "^1.0.17", "@kevisual/use-config": "^1.0.21",
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "^4.2.2",
"@types/formidable": "^3.4.5", "@types/node": "^25.0.3",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.15.29",
"@types/vosk": "^0.3.1",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"commander": "^14.0.0", "cross-env": "^10.1.0",
"concurrently": "^9.1.2", "dotenv": "^17.2.3",
"cross-env": "^7.0.3", "ioredis": "^5.8.2",
"dotenv": "^16.5.0", "rimraf": "^6.1.2",
"inquire": "^0.4.8",
"ioredis": "^5.6.1",
"nodemon": "^3.1.10",
"pg": "^8.16.0",
"pm2": "^6.0.6",
"rimraf": "^6.0.1",
"sequelize": "^6.37.7",
"tape": "^5.9.0",
"tsx": "^4.19.4",
"typescript": "^5.8.3",
"ws": "npm:@kevisual/ws" "ws": "npm:@kevisual/ws"
}, },
"exports": { "exports": {
"./src/*": "./src/*", "./src/*": "./src/*",
"./examples/*": "./examples/*" "./examples/*": "./examples/*"
}, },
"packageManager": "pnpm@10.11.1" "packageManager": "pnpm@10.26.1"
} }

3738
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
import { AsrRelatime as QwenAsrRelatime } from "./provider/aliyun/base.ts";
export {
QwenAsrRelatime
}

View File

@@ -1,42 +1,128 @@
import RPCClient from '@alicloud/pop-core';
interface TokenResponse { import { WSServer, WSSOptions } from '@kevisual/video-tools/asr/ws.ts';
Token: { type Options = {
Id: string; model?: string;
ExpireTime: number; token?: string;
}; } & Partial<WSSOptions>
}
type AliCommonOptions = { /**
accessKeyId: string; * 阿里云实时语音识别服务
accessKeySecret: string; * new AsrRelatime({
}; * token: 'your_token',
export class AliCommon { * model: 'general_16k',
private accessKeyId: string; * enableServerVad: true,
private accessKeySecret: string; * onConnect: async () => {
private endpoint: string; * await asr.sendSessionUpdate();
private apiVersion: string; * }
token = ''; * });
expireTime = 0; */
constructor(opts?: AliCommonOptions) { export class AsrRelatime extends WSServer {
this.accessKeyId = opts?.accessKeyId || process.env.ALIYUN_AK_ID || ''; static baseURL = "wss://dashscope.aliyuncs.com/api-ws/v1/realtime"
this.accessKeySecret = opts?.accessKeySecret || process.env.ALIYUN_AK_SECRET || ''; /**
this.endpoint = 'http://nls-meta.cn-shanghai.aliyuncs.com'; * 是否启用服务端VAD功能true为VAD模式false为Manual模式
this.apiVersion = '2019-02-28'; */
enableServerVad: boolean = true;
constructor(options: Options) {
const { url: _, wsOptions: _wsOptions, ...rest } = options;
const wsOptions: WSSOptions['wsOptions'] = {
..._wsOptions,
headers: {
Authorization: `Bearer ${options.token}`,
'OpenAi-Beta': 'realtime=v1',
..._wsOptions?.headers
}
};
const models = options.model || 'qwen3-asr-flash-realtime';
const url = AsrRelatime.baseURL + `?model=${models}`;
super({ ...rest, url, wsOptions, onConnect: options.onConnect });
} }
async getToken() { async sendSessionUpdate() {
if (this.token && this.expireTime > Date.now()) { const { ws, enableServerVad } = this;
return this.token; const connected = await this.checkConnected()
if (!connected) {
this.reconnect({ timeout: 60 * 1000 });
return;
}
const event = {
event_id: 'event_123',
type: 'session.update',
session: {
modalities: ['text'],
input_audio_format: 'pcm',
sample_rate: 16000,
input_audio_transcription: {
language: 'zh'
},
turn_detection: null
}
};
if (enableServerVad) {
event.session.turn_detection = {
type: 'server_vad',
threshold: 0.2,
silence_duration_ms: 800
}
}
ws.send(JSON.stringify(event));
}
async start() {
this.sendSessionUpdate();
}
async sendBuffer(buffer: Buffer) {
const { ws, enableServerVad } = this;;
const connected = await this.checkConnected()
if (!connected) {
this.reconnect({ timeout: 60 * 1000 });
return;
} }
const client = new RPCClient({
accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret,
endpoint: this.endpoint,
apiVersion: this.apiVersion,
});
const result = await client.request<TokenResponse>('CreateToken', {}); let offset = 0;
this.token = result.Token.Id; const bufferLength = Buffer.byteLength(buffer);
this.expireTime = result.Token.ExpireTime * 1000; const chunkSize = 3200; // 约0.1s的PCM16音频 // max lenghth 262144
return result.Token.Id; while (offset < bufferLength) {
const chunkBuffer = buffer.subarray(offset, offset + chunkSize);
offset += chunkSize;
const encoded = chunkBuffer.toString('base64');
const appendEvent = {
event_id: `event_${Date.now()}`,
type: 'input_audio_buffer.append',
audio: encoded
};
ws.send(JSON.stringify(appendEvent));
}
if (!enableServerVad) {
const commitEvent = {
event_id: 'event_789',
type: 'input_audio_buffer.commit'
};
ws.send(JSON.stringify(commitEvent));
}
}
async onMessage(event: MessageEvent) {
super.onMessage(event);
const data = event.data;
try {
const result = JSON.parse(data.toString());
const isEnd = await this.isEnd(result.type);
if (isEnd && result?.transcript) {
const text = result.transcript;
this.emitter.emit('result', {
text: text,
raw: result
});
}
} catch (error) {
console.log('error', error);
}
}
async isEnd(type: string) {
const types = ['conversation.item.input_audio_transcription.text', 'conversation.item.input_audio_transcription.completed'];
if (type === types[1]) {
return true;
}
return false;
}
async sendBlank(buffer?: Buffer): Promise<void> {
this.sendBuffer(buffer || this.generateSilence(2));
} }
} }

View File

@@ -75,6 +75,7 @@ export class AliAsrServer {
const response = await fetch(requestUrl, { const response = await fetch(requestUrl, {
method: 'POST', method: 'POST',
headers, headers,
// @ts-ignore
body: audioContent, body: audioContent,
}); });

View File

@@ -0,0 +1,42 @@
import RPCClient from '@alicloud/pop-core';
interface TokenResponse {
Token: {
Id: string;
ExpireTime: number;
};
}
type AliCommonOptions = {
accessKeyId: string;
accessKeySecret: string;
};
export class AliCommon {
private accessKeyId: string;
private accessKeySecret: string;
private endpoint: string;
private apiVersion: string;
token = '';
expireTime = 0;
constructor(opts?: AliCommonOptions) {
this.accessKeyId = opts?.accessKeyId || process.env.ALIYUN_AK_ID || '';
this.accessKeySecret = opts?.accessKeySecret || process.env.ALIYUN_AK_SECRET || '';
this.endpoint = 'http://nls-meta.cn-shanghai.aliyuncs.com';
this.apiVersion = '2019-02-28';
}
async getToken() {
if (this.token && this.expireTime > Date.now()) {
return this.token;
}
const client = new RPCClient({
accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret,
endpoint: this.endpoint,
apiVersion: this.apiVersion,
});
const result = await client.request<TokenResponse>('CreateToken', {});
this.token = result.Token.Id;
this.expireTime = result.Token.ExpireTime * 1000;
return result.Token.Id;
}
}

View File

@@ -0,0 +1 @@
通过access_id和access_key_secret使用阿里云智能语音服务进行语音识别。

View File

@@ -0,0 +1,25 @@
import { AliAsrServer } from '../aliyun-asr-server.ts';
import fs from 'fs/promises';
import path from 'path';
// const videoTestPath = path.join(process.cwd(), 'videos/asr_example.wav');
// const videoTestPath = path.join(process.cwd(), 'videos/asr_example2.wav');
// const videoTestPath = path.join(process.cwd(), 'videos/tts_mix.mp3');
const videoTestPath = path.join(process.cwd(), 'videos/my_speech_text.wav');
const name = 'output-1746007775571.mp3';
const videoTestPath2 = path.join(process.cwd(), 'build', name);
// 使用示例
async function main() {
const asrServer = new AliAsrServer({
appkey: process.env.ALI_ASR_APP_KEY,
token: process.env.ALI_ASR_TOKEN,
format: 'mp3',
// format: 'wav',
});
const audioContent = await fs.readFile(videoTestPath);
await asrServer.processAudio(audioContent);
}
// 执行主函数
main().catch(console.error);

View File

@@ -1,25 +1,53 @@
import { AliAsrServer } from '../aliyun-asr-server.ts'; import { AsrRelatime } from '../base.ts';
import fs from 'fs/promises'; import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import net from 'net';
import dotenv from 'dotenv';
dotenv.config();
// const videoTestPath = path.join(process.cwd(), 'videos/asr_example.wav'); // const videoTestPath = path.join(process.cwd(), 'videos/asr_example.wav');
// const videoTestPath = path.join(process.cwd(), 'videos/asr_example2.wav'); // const videoTestPath = path.join(process.cwd(), 'videos/asr_example2.wav');
// const videoTestPath = path.join(process.cwd(), 'videos/tts_mix.mp3'); // const videoTestPath = path.join(process.cwd(), 'videos/tts_mix.mp3');
const videoTestPath = path.join(process.cwd(), 'videos/my_speech_text.wav'); const videoTestPath = path.join(process.cwd(), 'videos/my_speech_text.wav');
const name = 'output-1746007775571.mp3'; const videoTestPath2 = path.join(process.cwd(), 'videos/asr_example2.wav');
const videoTestPath2 = path.join(process.cwd(), 'build', name); const videoBlankPath = path.join(process.cwd(), 'videos/blank.wav');
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// 使用示例 // 使用示例
async function main() { async function main() {
const asrServer = new AliAsrServer({ const ws = new AsrRelatime({
appkey: process.env.ALI_ASR_APP_KEY, token: process.env.BAILIAN_API_KEY,
token: process.env.ALI_ASR_TOKEN, onConnect: async () => {
format: 'mp3', ws.emitter.on('message', (event) => {
// format: 'wav', // console.log('message', event.data);
});
ws.emitter.on('result', ({ text }) => {
console.log('result:', text);
});
ws.emitter.on('close', (event) => {
const { code, reason, type } = event || {};
console.log('Connection closed:', code, reason, type);
if (typeof code === 'number' && code !== 0) {
ws.reconnect({ timeout: 1000 });
}
});
await ws.start();
const audioContent = await fs.readFile(videoTestPath);
ws.sendBuffer(audioContent);
ws.sendBlank();
ws.sendBuffer(await fs.readFile(videoTestPath2));
ws.sendBlank();
},
}); });
const audioContent = await fs.readFile(videoTestPath); //
await asrServer.processAudio(audioContent);
} }
// 执行主函数 // 执行主函数
main().catch(console.error); main().catch(console.error);
const server = net.createServer((socket) => {
socket.on('data', (data) => {
console.log('data', data);
});
});
server.listen(10096);

View File

@@ -0,0 +1,61 @@
import { AsrRelatime } from '../base.ts';
import path from 'node:path';
import net from 'net';
import { Recording } from '../../../../recorder/index.ts';
import Stream from 'stream';
import fs from 'node:fs'; // 新增
const recorder = new Recording({
sampleRate: 16000,
channels: 1, //
audioType: 'wav',
threshold: 0,
recorder: 'sox',
silence: '1.0',
endOnSilence: true,
});
const writeFilePath = path.join(process.cwd(), 'funasr_test.wav');
const fileStream = fs.createWriteStream(writeFilePath, { encoding: 'binary' });
const url = 'wss://funasr.xiongxiao.me';
const url3 = 'wss://pro.xiongxiao.me:10095';
const url4 = 'wss://121.4.112.18:10095'; // aliyun
const url5 = 'https://1.15.101.247:10095'; // pro
const ws = new AsrRelatime({
onConnect: async () => {
console.log('onConnect');
ws.sendSessionUpdate();
recorder.start();
let len = 0;
recorder.stream().on('data', (chunk) => {
// ws.sendBuffer(chunk, { online: true });
// console.log('Sending audio chunk:', chunk.length);
ws.sendBuffer(chunk);
fileStream.write(chunk); // 新增:将音频数据写入文件
len += chunk.length;
});
setTimeout(() => {
// ws.stop();
fileStream.end(); // 新增:关闭文件流
setTimeout(() => {
process.exit(0);
}, 1000);
console.log('len', len);
}, 10 * 1000);
ws.emitter.on('result', (event) => {
console.log('result', event);
});
},
});
const server = net.createServer((socket) => {
socket.on('data', (data) => {
console.log('data', data);
});
});
server.listen(10097);

View File

@@ -1,6 +1,6 @@
// import WebSocket from 'ws'; // import WebSocket from 'ws';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { WSServer, WSSOptions } from '../../provider/ws-server.ts'; import { WSServer, WSSOptions } from '../../ws.ts';
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export type VideoWSOptions = { export type VideoWSOptions = {
url?: string; url?: string;

View File

@@ -1,4 +1,4 @@
import { WSServer } from '../../provider/ws-server.ts'; import { WSServer } from '../../ws.ts';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
export const uuid = () => nanoid(16); export const uuid = () => nanoid(16);

View File

@@ -1,98 +0,0 @@
import { EventEmitter } from 'eventemitter3';
import { initWs } from '../../ws-adapter/index.ts';
import type { ClientOptions } from 'ws';
export type WSSOptions = {
url: string;
ws?: WebSocket;
onConnect?: () => void;
wsOptions?: ClientOptions;
emitter?: EventEmitter;
};
export class WSServer {
ws: WebSocket;
onConnect?: () => void;
connected: boolean;
emitter: EventEmitter;
url: string;
wsOptions?: ClientOptions;
constructor(opts: WSSOptions) {
this.connected = false;
this.url = opts.url;
this.wsOptions = opts.wsOptions;
this.initWs(opts);
}
async initWs(opts: WSSOptions) {
if (opts.ws) {
this.ws = opts.ws;
}
this.emitter = opts.emitter || new EventEmitter();
this.ws = await initWs(opts.url, opts.wsOptions);
this.onConnect = opts?.onConnect || (() => {});
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
async reconnect() {
this.ws = await initWs(this.url, this.wsOptions);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
/**
* 连接成功 ws 事件
*/
async onOpen() {
this.connected = true;
this?.onConnect?.();
this.emitter.emit('open');
}
/**
* 检查是否连接
* @returns
*/
async isConnected() {
if (this.connected) {
return true;
}
return new Promise((resolve) => {
this.emitter.once('open', () => {
resolve(true);
});
});
}
/**
* 收到消息 ws 事件
* @param event
*/
async onMessage(event: MessageEvent) {
// console.log('WSS onMessage', event);
this.emitter.emit('message', event);
}
/**
* ws 错误事件
* @param event
*/
async onError(event: Event) {
console.error('WSS onError');
this.emitter.emit('error', event);
}
/**
* ws 关闭事件
* @param event
*/
async onClose(event: CloseEvent) {
console.error('WSS onClose');
this.emitter.emit('close', event);
this.connected = false;
}
/**
* 关闭 ws 连接
*/
async close() {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
}
}

222
src/asr/ws.ts Normal file
View File

@@ -0,0 +1,222 @@
import { EventEmitter } from 'eventemitter3';
import { initWs } from '../ws/index.ts';
import type { ClientOptions } from 'ws';
export type WSSOptions = {
url: string;
ws?: WebSocket;
onConnect?: () => void;
wsOptions?: ClientOptions;
emitter?: EventEmitter;
};
interface WSServerInterface {
isComplated(type: string, endType?: string): Promise<boolean>;
start(): Promise<void>;
}
export class WSServer implements WSServerInterface {
ws: WebSocket;
onConnect?: () => void;
connected: boolean;
connecting: boolean = false;
emitter: EventEmitter;
url: string;
wsOptions?: ClientOptions;
constructor(opts: WSSOptions) {
this.connected = false;
this.url = opts.url;
this.wsOptions = opts.wsOptions;
this.emitter = opts?.emitter || new EventEmitter();
this.onConnect = opts?.onConnect || (() => { });
this.initWs();
}
async initWs(opts?: WSSOptions) {
if (opts?.ws) {
this.ws = opts.ws;
}
if (opts?.emitter) {
this.emitter = opts.emitter;
}
if (opts?.onConnect) {
this.onConnect = opts.onConnect;
}
if (opts?.emitter) {
this.emitter = opts.emitter;
}
this.connecting = true;
this.ws = await initWs(this.url, this.wsOptions);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
async reconnect(opts?: { timeout?: number }) {
if (this.connected || this.connecting) {
return;
}
this.connecting = true;
const timeout = opts?.timeout || 0;
if (timeout > 0) {
await new Promise((resolve) => setTimeout(resolve, timeout));
}
this.ws = await initWs(this.url, this.wsOptions);
this.ws.onopen = this.onOpen.bind(this);
this.ws.onmessage = this.onMessage.bind(this);
this.ws.onerror = this.onError.bind(this);
this.ws.onclose = this.onClose.bind(this);
}
async start() {
// do nothing
}
/**
* 连接成功 ws 事件
*/
async onOpen() {
this.connected = true;
this.connecting = false;
this?.onConnect?.();
this.emitter.emit('open');
}
/**
* 检查是否连接
* @returns
*/
async isConnected() {
if (this.connected) {
return true;
}
return new Promise((resolve) => {
const timeout = setTimeout(() => {
resolve(false);
}, 5000);
this.emitter.once('open', () => {
clearTimeout(timeout);
resolve(true);
});
});
}
/**
* 收到消息 ws 事件
* @param event
*/
async onMessage(event: MessageEvent) {
// console.log('WSS onMessage', event);
this.emitter.emit('message', event);
}
/**
* ws 错误事件
* @param event
*/
async onError(event: Event) {
// console.error('WSS onError');
this.emitter.emit('error', event);
}
/**
* ws 关闭事件
* @param event
*/
async onClose(event: CloseEvent) {
this.emitter.emit('close', event);
this.connected = false;
setTimeout(() => {
this.emitter.removeAllListeners();
}, 100);
const { code } = event;
if (typeof code === 'number' && code !== 0) {
this.reconnect({ timeout: 10000 });
}
}
/**
* 检查并连接 ws
* @returns
*/
async checkConnected() {
const connecting = this.connecting
if (connecting) {
return this.isConnected();
}
if (!this.connected) {
await this.reconnect();
return this.isConnected();
}
return true;
}
/**
* 关闭 ws 连接
*/
async close() {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
}
async isComplated(type: string, endType = '') {
if (type === endType) {
return true;
}
return false;
}
/**
* 生成指定时长的静音 WAV 音频缓冲区
* @param durationSeconds 静音时长(秒)
* @returns WAV 音频缓冲区
*/
generateSilence(durationSeconds: number): Buffer {
const sampleRate = 16000; // 采样率 16kHz
const bitDepth = 16; // 位深 16bit
const channels = 1; // 单声道
const duration = durationSeconds; // 时长
const samplesPerChannel = sampleRate * duration;
const bytesPerSample = bitDepth / 8;
const blockAlign = channels * bytesPerSample;
const byteRate = sampleRate * blockAlign;
const dataSize = samplesPerChannel * blockAlign;
const fileSize = 36 + dataSize;
// 创建 WAV 文件头
const header = Buffer.alloc(44);
let offset = 0;
// RIFF 标识
header.write('RIFF', offset); offset += 4;
header.writeUInt32LE(fileSize, offset); offset += 4;
header.write('WAVE', offset); offset += 4;
// fmt 子块
header.write('fmt ', offset); offset += 4;
header.writeUInt32LE(16, offset); offset += 4; // fmt 块大小
header.writeUInt16LE(1, offset); offset += 2; // PCM 格式
header.writeUInt16LE(channels, offset); offset += 2; // 声道数
header.writeUInt32LE(sampleRate, offset); offset += 4; // 采样率
header.writeUInt32LE(byteRate, offset); offset += 4; // 字节率
header.writeUInt16LE(blockAlign, offset); offset += 2; // 块对齐
header.writeUInt16LE(bitDepth, offset); offset += 2; // 位深
// data 子块
header.write('data', offset); offset += 4;
header.writeUInt32LE(dataSize, offset);
// 创建静音数据全为0
const silenceData = Buffer.alloc(dataSize);
// 合并头部和数据
return Buffer.concat([header, silenceData]);
}
async sendBlank(buffer?: Buffer) {
const isConnected = await this.checkConnected();
if (!isConnected) {
this.reconnect({ timeout: 1000 });
return;
}
if (buffer) {
this.ws.send(buffer);
return;
}
// 生成一个 2 秒静音
const blankBuffer = this.generateSilence(2);
this.ws.send(blankBuffer);
}
}

View File

@@ -1,7 +1,7 @@
import assert from 'assert'; import assert from 'assert';
import { logger } from '../logger/index.ts'; import { logger } from '../logger/index.ts';
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; import { ChildProcessWithoutNullStreams, spawn } from 'child_process';
import recorders from '../recorder/recorders/index.ts'; import recorders from './recorders/index.ts';
import Stream from 'stream'; import Stream from 'stream';
export type RecordingOptions = { export type RecordingOptions = {
/* 采样率默认为16000 */ /* 采样率默认为16000 */

View File

@@ -34,7 +34,7 @@ export const initWs = async (url: string, options?: WebSocketOptions) => {
} }
return ws; return ws;
}; };
interface EventEmitterOptions { export interface EventEmitterOptions {
/** /**
* Enables automatic capturing of promise rejection. * Enables automatic capturing of promise rejection.
*/ */

View File

@@ -1,33 +1,27 @@
{ {
"extends": "@kevisual/types/json/backend.json",
"compilerOptions": { "compilerOptions": {
"module": "nodenext", "module": "NodeNext",
"target": "esnext", "target": "esnext",
"noImplicitAny": false, "baseUrl": ".",
"outDir": "./dist",
"sourceMap": false,
"allowJs": true,
"newLine": "LF",
"baseUrl": "./",
"typeRoots": [ "typeRoots": [
"node_modules/@types", "./node_modules/@types",
"node_modules/@kevisual/types" "./node_modules/@kevisual/types/index.d.ts"
], ],
"declaration": true,
"noEmit": false,
"allowImportingTsExtensions": true,
"emitDeclarationOnly": true,
"moduleResolution": "NodeNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"paths": { "paths": {
"@/*": [ "@/*": [
"src/*" "src/*"
],
"@agent/*": [
"agent/*"
],
"@kevisual/video-tools/*": [
"src/*"
] ]
} },
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*",
"agent/**/*",
], ],
"exclude": [],
} }