npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

echo-voice-sdk

v1.5.1

Published

前端离线语音控制SDK,基于 Vosk 实现完全离线的语音识别,内置中文语音模型

Readme

EchoSDK - 前端离线语音控制 SDK

EchoSDK 是一个基于 Vosk 的前端离线语音控制 SDK,支持完全离线的语音识别、VAD 检测、声纹识别、唤醒词检测、TTS 语音合成和指令解析。v1.5.1 修复了浏览器 TTS 播放问题

🎯 识别准确度不高? 查看 快速开始指南优化指南

✨ 功能特性

  • 🎤 连续监听 - 实时监听用户语音输入
  • 🎯 精准 VAD - 基于多特征融合算法,准确判断语音开始和结束
  • 📝 Vosk 离线识别 - 基于 Vosk WASM,完全离线的中文语音识别
  • 🔊 声纹识别 - 基于 MFCC 特征提取,支持说话人识别和验证
  • 🗣️ 唤醒词检测 - 基于语音识别的唤醒词检测,无需录制模板,开箱即用
  • 📢 TTS 语音合成 - 基于 Web Speech API 的语音合成,支持唤醒回复
  • 🎮 指令解析 - 支持模糊匹配、拼音匹配、同义词匹配、参数提取、上下文管理
  • 🔤 拼音匹配 - 解决语音识别同音字问题,如"向佐"匹配"向左"
  • 📚 同义词匹配 - 识别同义词表达,如"打开"="开启"="启动"
  • 🔍 关键词提取 - 优化短句匹配,提取关键词进行智能匹配
  • 🧠 智能文本处理 - 自动标准化口语化表达,处理中文数字,修正多音字
  • 🔐 权限管理 - 自动检查和请求麦克风权限,智能引导用户授权
  • 🚀 完全离线 - 所有功能在浏览器本地运行,无需网络连接

🆕 v1.5.1 更新内容

TTS 语音播放修复 🔊

修复了浏览器 TTS 播放一次后无法再次播放的问题

  1. 问题描述

    • 在某些浏览器(特别是 Chrome)中,使用 speechSynthesis API 播放语音后,再次调用或刷新页面都无法播放
    • 这是浏览器 speechSynthesis API 的已知 bug
  2. 修复方案

    • 播放前调用 cancel() 清理浏览器状态
    • 使用 setTimeout 延迟调用 speak(),避免竞态条件
    • stop()destroy() 中确保完全清理状态
    • 双重 cancel() 调用确保彻底清理
  3. 测试验证

    • 新增 examples/test-tts-fix.html 测试页面
    • 包含连续播放、快速点击、刷新后播放三个测试场景

TTS 回声过滤功能 🔇

新增 TTS 回声过滤功能,解决 TTS 播放内容被语音识别捕获的问题

  1. 问题描述

    • TTS 播放时,声音通过扬声器输出,被麦克风捕获
    • 语音识别器会识别 TTS 播放的内容,形成"回声"
  2. 解决方案

    • 方案3:回声消除 - 使用浏览器的 echoCancellation 选项
    • 方案4:TTS 内容过滤 - 记录 TTS 播放的内容,在识别结果中过滤
  3. 使用方法

// 回声过滤默认启用,可以手动控制
sdk.enableEchoFiltering();   // 启用
sdk.disableEchoFiltering();  // 禁用

// 设置过滤相似度阈值(0-1,越低越严格)
sdk.setEchoFilterSimilarity(0.8);

// 设置过滤超时时间(毫秒)
sdk.setEchoFilterTimeout(10000);

// 手动检查文本是否是 TTS 播放的内容
const isEcho = sdk.isSpokenText('你好');

// 手动过滤文本中的 TTS 内容
const filtered = sdk.filterSpokenText('你好世界');

新增 API

// TTS 回声过滤
sdk.enableEchoFiltering();        // 启用回声过滤
sdk.disableEchoFiltering();       // 禁用回声过滤
sdk.isEchoFilteringEnabled();     // 检查是否启用
sdk.setEchoFilterSimilarity(0.8); // 设置相似度阈值
sdk.setEchoFilterTimeout(10000);  // 设置过滤超时
sdk.isSpokenText(text);           // 检查是否是 TTS 内容
sdk.filterSpokenText(text);       // 过滤 TTS 内容
sdk.getRecentSpokenTexts();       // 获取最近播放的文本
sdk.clearSpokenTexts();           // 清空播放记录

使用建议

// 如果仍有问题,可以手动重置
window.speechSynthesis.cancel();
await sdk.speak('你好');

🆕 v1.5.0 更新内容

识别准确度优化 🎯

  1. 多音字智能识别

    • 根据上下文自动修正多音字
    • 如:"向佐飞" → "向左飞","往有转" → "往右转"
    • 支持自定义多音字映射规则
  2. 同义词匹配

    • 内置丰富的同义词库(开关、方向、颜色、设备等)
    • 自动识别同义词表达,如"打开"="开启"="启动"
    • 支持自定义同义词组
  3. 关键词提取优化

    • 智能提取关键词进行匹配
    • 优化短句识别准确度
    • 自动过滤停用词和无用词汇
  4. 智能文本标准化

    • 自动处理口语化表达(如:"给我"、"帮我"、"麻烦")
    • 中文数字自动转换(如:"二十三" → "23")
    • 标点符号和空格智能处理
  5. 拼音匹配阈值优化

    • 默认阈值从 1.0 降低到 0.7,提高匹配成功率
    • 支持动态调整阈值

新增 API

// 同义词匹配
sdk.enableSynonymMatch();
sdk.disableSynonymMatch();
sdk.addSynonymGroup('飞行', ['飞', '起飞', '飞起来']);

// 关键词提取
sdk.enableKeywordExtraction();
sdk.disableKeywordExtraction();

示例文件

  • 新增 examples/demo-accuracy.html - 识别准确度优化演示

🆕 v1.4.0 更新内容

新增功能

  1. 自动权限检查

    • SDK 初始化时自动检查麦克风和 TTS 权限
    • 权限被拒绝时自动弹出引导弹窗
    • 根据浏览器类型显示详细的操作步骤
  2. 权限管理 API

    • checkAllPermissions() - 检查所有权限状态
    • requestPermissions() - 请求权限(带弹窗提示)
    • onPermissionChange() - 监听权限变化
    • getPermissionStatus() - 获取当前权限状态
  3. 自定义权限弹窗

    • 支持自定义弹窗渲染函数
    • 内置美观的默认弹窗样式
    • 支持自定义文案和按钮
  4. 智能权限引导

    • 自动识别浏览器类型(Chrome、Firefox、Safari、Edge)
    • 提供针对性的操作指引
    • 步骤清晰,易于理解

修复问题

  1. TTS 多次调用问题

    • 修复快速连续调用 speak() 导致的 Promise 重复 resolve 问题
    • 优化取消逻辑,避免旧任务影响新任务
    • 改进错误处理,区分正常取消和真正的错误
  2. TTS 自动播放限制

    • 优雅处理 not-allowed 错误
    • 提供清晰的错误提示和解决方案
    • 支持在用户交互后重试

示例文件

  • 新增 examples/demo-permission.html - 权限管理演示

📦 安装

npm install echo-voice-sdk

🚀 快速开始

在线 Demo

启动本地服务器后访问:

npm run demo

然后打开浏览器访问以下 Demo 页面:

| Demo 文件 | 功能说明 | 访问地址 | |-----------|----------|----------| | demo.html | 基础语音识别 - Vosk 离线识别 + 指令匹配 + 声纹识别 + 唤醒词 | http://127.0.0.1:8080/examples/demo.html | | demo-sdk.html | SDK 完整功能 - 使用 SDK 封装的 API,包含拼音匹配功能 | http://127.0.0.1:8080/examples/demo-sdk.html | | demo-voiceprint.html | 声纹识别专项 - 声纹录制、注册、识别的完整演示 | http://127.0.0.1:8080/examples/demo-voiceprint.html | | demo-permission.html | 权限管理演示 - 自动权限检查、请求权限、智能引导 | http://127.0.0.1:8080/examples/demo-permission.html | | demo-accuracy.html | 识别准确度优化 - 多音字、同义词、短句匹配优化演示 | http://127.0.0.1:8080/examples/demo-accuracy.html |

💡 拼音匹配功能:SDK 内置拼音匹配,可解决同音字问题(如"向佐"→"向左"),所有 Demo 均已支持

引入方式

SDK 提供三种版本:

| 文件 | 大小 | 说明 | |------|------|------| | echo-sdk.js | ~5.6MB | 完整版,内置 Vosk,推荐使用 | | echo-sdk.esm.js | ~80KB | ESM 模块版,支持 tree-shaking | | echo-sdk.lite.js | ~34KB | 轻量版,需单独引入 Vosk |

方式一:完整版(推荐)

<!-- 只需引入 SDK,Vosk 已内置 -->
<script src="dist/echo-sdk.js"></script>
<script>
  // Vosk 可通过 EchoSDK.Vosk 访问
  const Vosk = EchoSDK.Vosk;
</script>

方式二:轻量版(需单独引入 Vosk)

<!-- 先引入 Vosk -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vosk.js"></script>
<!-- 再引入轻量版 SDK -->
<script src="dist/echo-sdk.lite.js"></script>

方式三:ESM 模块

import EchoSDK, { Vosk } from 'echo-voice-sdk';

基本使用

import EchoSDK from 'echo-voice-sdk';

// 创建 SDK 实例
const sdk = new EchoSDK({
  debug: true,
  speechRecognition: {
    // Vosk 模型路径,支持以下方式:
    // 1. 本地路径(需要将模型文件放到你的静态资源目录)
    voskModelPath: '/models/vosk-model-cn.tar.gz'
    // 2. CDN 远程地址
    // voskModelPath: 'https://your-cdn.com/vosk-model-cn.tar.gz'
  },
  // 唤醒词配置(可选)
  wakeWord: {
    enabled: true,              // 启用唤醒词
    words: ['小云小云'],         // 唤醒词列表
    sensitivity: 0.6,           // 灵敏度 0-1
    response: '我在',           // 唤醒后的语音回复
    commandTimeout: 10000       // 唤醒后等待指令的超时时间 (ms)
  },
  // TTS 语音合成配置(可选)
  tts: {
    enabled: true,
    lang: 'zh-CN',
    rate: 1.0
  },
  commands: [
    {
      name: 'openLight',
      triggers: ['打开灯', '开灯'],
      handler: (params) => {
        console.log('执行: 打开灯');
      }
    }
  ]
});

// 注册事件监听
sdk.on('voiceStart', () => console.log('语音开始'));
sdk.on('voiceEnd', (data) => console.log('语音结束,时长:', data.duration, 'ms'));
sdk.on('recognition', (result) => console.log('识别结果:', result.text));
sdk.on('command', (result) => console.log('指令:', result.command));
sdk.on('wake', (result) => console.log('唤醒词:', result.word, '置信度:', result.confidence));
sdk.on('error', (error) => console.error('错误:', error.message));

// 初始化并启动
await sdk.init();
await sdk.start();

🔄 SDK 生命周期

生命周期方法

// 初始化 SDK(加载模型、初始化各模块)
await sdk.init();

// 开始语音监听
await sdk.start();

// 停止语音监听(可以重新 start)
sdk.stop();

// 销毁 SDK(释放所有资源,不可恢复)
sdk.destroy();

⚠️ 常见问题

1. Vue 中使用报错:buffer.getChannelData is not a function

问题原因:vosk-browser 的 acceptWaveform 方法需要 AudioBuffer 对象,而不是 Float32Array

解决方案:SDK 已在 v1.0.1 版本中修复此问题。如果仍遇到此错误,请确保:

  1. 使用最新版本的 SDK:
npm install echo-voice-sdk@latest
  1. 清除缓存并重新构建:
rm -rf node_modules package-lock.json
npm install
npm run build
  1. 在 Vue 项目中正确引入:
import EchoSDK from 'echo-voice-sdk';

// 在 Vue 组件中使用
export default {
  async mounted() {
    const sdk = new EchoSDK({
      debug: true,
      speechRecognition: {
        voskModelPath: '/models/vosk-model-cn.tar.gz',
      },
      // 唤醒词配置(可选)
      wakeWord: {
        enabled: true,              // 启用唤醒词
        words: ['小云小云'],         // 唤醒词列表
        sensitivity: 0.6,           // 灵敏度 0-1
        response: '我在',           // 唤醒后的语音回复
        commandTimeout: 10000       // 唤醒后等待指令的超时时间 (ms)
      },
      // TTS 语音合成配置(可选)
      tts: {
        enabled: true,
        lang: 'zh-CN',
        rate: 1.0
      },
      commands: [
        {
          name: 'openLight',
          triggers: ['打开灯', '开灯'],
          handler: (params) => {
            console.log(params);
          },
        },
      ],
    });

    sdk.on('voiceStart', () => console.log('语音开始'));
    sdk.on('voiceEnd', (data) => console.log('语音结束,时长:', data.duration, 'ms'));
    sdk.on('recognition', (result) => console.log('识别结果:', result.text));
    sdk.on('command', (result) => console.log('指令:', result.command));
    sdk.on('wake', (result) => console.log('唤醒词:', result.word));
    sdk.on('error', (error) => console.error('错误:', error.message));

    await sdk.init();
    await sdk.start();
  }
}

2. TTS 播放时报错:canceledinterrupted

问题原因:当 TTS 语音被取消或打断时(例如快速连续调用 speak() 或调用 stop()),Web Speech API 会触发 onerror 事件,错误类型为 canceledinterrupted。这是正常的取消行为,不应该被当作真正的错误。

解决方案:SDK 已在最新版本中修复此问题,canceledinterrupted 错误会被正常处理,不会抛出异常。如果仍遇到此问题,请更新到最新版本:

npm install echo-voice-sdk@latest

修复内容

  • canceled 错误:正常的取消操作(如调用 stop() 或新的 speak() 调用)
  • interrupted 错误:被其他语音打断,也是正常情况
  • 这两种情况现在会正常结束 Promise,不会抛出错误
  • 只有真正的错误(如 audio-busynetwork 等)才会触发异常

3. TTS 播放时报错:not-allowed

问题原因:浏览器的自动播放策略(Autoplay Policy)要求 TTS 语音合成必须由用户交互触发(如点击、触摸等),不能在页面加载时自动播放。

错误示例

// ❌ 错误:在页面加载时直接播放
async function init() {
  const sdk = new EchoSDK({ tts: { enabled: true } });
  await sdk.init();
  await sdk.speak('欢迎使用'); // 抛出 not-allowed 错误
}

解决方案一:在用户交互后播放(推荐)

// ✅ 正确:先初始化,在用户点击后播放
let sdk = null;

async function init() {
  sdk = new EchoSDK({ tts: { enabled: true } });
  await sdk.init();
  return sdk;
}

// 在按钮点击事件中播放
button.addEventListener('click', async () => {
  if (!sdk) {
    await init();
  }
  await sdk.speak('欢迎使用'); // 正常播放
  await sdk.start();
});

解决方案二:捕获错误并提示用户

async function init() {
  const sdk = new EchoSDK({ tts: { enabled: true } });
  await sdk.init();
  
  try {
    await sdk.speak('欢迎使用');
  } catch (error) {
    if (error.code === 'NOT_ALLOWED') {
      console.log('语音播放需要用户交互,请点击按钮后再试');
      // 显示提示按钮让用户点击
    }
  }
  
  return sdk;
}

Vue 示例

<template>
  <div>
    <button @click="handleStart">开始使用语音助手</button>
  </div>
</template>

<script setup>
import EchoSDK from 'echo-voice-sdk';

let sdk = null;

const handleStart = async () => {
  if (!sdk) {
    sdk = new EchoSDK({ 
      tts: { enabled: true },
      speechRecognition: {
        voskModelPath: '/models/vosk-model-cn.tar.gz'
      }
    });
    await sdk.init();
  }
  
  // 在用户点击后播放,不会报错
  await sdk.speak('欢迎使用语音助手');
  await sdk.start();
};
</script>

React 示例

import { useState } from 'react';
import EchoSDK from 'echo-voice-sdk';

function App() {
  const [sdk, setSdk] = useState(null);
  
  const handleStart = async () => {
    let instance = sdk;
    
    if (!instance) {
      instance = new EchoSDK({ 
        tts: { enabled: true },
        speechRecognition: {
          voskModelPath: '/models/vosk-model-cn.tar.gz'
        }
      });
      await instance.init();
      setSdk(instance);
    }
    
    // 在用户点击后播放,不会报错
    await instance.speak('欢迎使用语音助手');
    await instance.start();
  };
  
  return (
    <button onClick={handleStart}>开始使用语音助手</button>
  );
}

4. 模型加载失败

问题原因:Vosk 模型文件路径不正确或文件不存在。

解决方案

  1. 确保模型文件已复制到静态资源目录
  2. 检查 voskModelPath 配置是否正确
  3. 查看浏览器控制台的网络请求,确认模型文件是否成功加载

4. 麦克风权限被拒绝

问题原因:浏览器需要用户授权才能访问麦克风。

解决方案

SDK 已在最新版本中自动集成权限检查功能,在初始化时会自动检测权限状态:

  1. 自动检查init() 时自动检查权限
  2. 自动弹窗:如果权限被拒绝,自动显示引导弹窗
  3. 操作指引:根据浏览器类型显示详细的操作步骤
const sdk = new EchoSDK({ /* 配置 */ });

// 初始化时自动检查权限
try {
  await sdk.init(); // 如果权限被拒绝,会自动弹出引导弹窗
} catch (error) {
  if (error.message === '麦克风权限被拒绝') {
    console.log('用户拒绝了权限或需要手动开启');
  }
}

手动控制权限检查

如果你想在初始化前手动检查权限:

const sdk = new EchoSDK({ /* 配置 */ });

// 先检查权限
const permissionResult = await sdk.checkAllPermissions();

if (permissionResult.hasDenied) {
  // 显示自定义的权限引导 UI
  console.log('权限被拒绝,请手动开启');
} else if (permissionResult.microphone === 'prompt') {
  // 请求权限
  await sdk.requestPermissions({
    showDialog: true,
    dialogTitle: '需要授权',
    dialogMessage: '为了使用语音功能,需要您授权麦克风权限'
  });
}

// 然后初始化
await sdk.init();

在 HTTPS 环境下运行

确保在 HTTPS 环境下运行(localhost 除外),否则浏览器不允许访问麦克风。

📖 配置选项

| 配置项 | 类型 | 默认值 | 描述 | |-------|------|--------|------| | sampleRate | number | 16000 | 音频采样率 | | bufferSize | number | 4096 | 音频缓冲区大小 | | language | string | 'zh-CN' | 语言 | | debug | boolean | false | 调试模式 | | autoStart | boolean | false | 是否自动开始监听 | | vad | VADConfig | - | VAD 配置 | | speechRecognition | SpeechRecognitionConfig | - | 语音识别配置 | | voiceprint | VoiceprintConfig | - | 声纹识别配置 | | wakeWord | WakeWordConfig | - | 唤醒词配置 | | tts | TTSConfig | - | TTS 语音合成配置 | | commandParser | CommandParserConfig | - | 指令解析器配置 | | commands | Command[] | [] | 指令列表 | | events | EventListeners | - | 事件监听器(可在初始化时预设) |

完整配置示例

const sdk = new EchoSDK({
  // 基础配置
  sampleRate: 16000,
  bufferSize: 4096,
  language: 'zh-CN',
  debug: true,
  autoStart: false,

  // VAD 配置
  vad: {
    mode: 2,                    // 灵敏度模式 0-3
    silenceThreshold: 500,      // 静音时长阈值 (ms)
    voiceThreshold: 200         // 语音起始阈值 (ms)
  },

  // 语音识别配置
  speechRecognition: {
    language: 'zh-CN',
    continuous: false,
    interimResults: true,
    voskModelPath: '/models/vosk-model-cn.tar.gz',
    maxDuration: 10000
  },

  // 声纹识别配置
  voiceprint: {
    enabled: true,
    threshold: 0.88,            // 匹配阈值
    minEnrollDuration: 5000     // 最小录入时长 (ms)
  },

  // 唤醒词配置
  wakeWord: {
    enabled: true,
    words: ['小云小云', '你好小云'],
    sensitivity: 0.6,
    response: '我在',
    commandTimeout: 10000,
    fuzzyMatch: true,
    minSimilarity: 0.8
  },

  // TTS 配置
  tts: {
    enabled: true,
    lang: 'zh-CN',
    rate: 1.0,
    pitch: 1.0,
    volume: 1.0,
    voiceName: 'Microsoft Xiaoxiao'
  },

  // 指令解析器配置
  commandParser: {
    enablePinyinMatch: true,           // 启用拼音匹配
    pinyinMatchThreshold: 0.8,         // 拼音匹配阈值
    enableSynonymMatch: true,          // 启用同义词匹配
    enableKeywordExtraction: true      // 启用关键词提取
  },

  // 指令列表
  commands: [
    {
      name: 'openLight',
      triggers: ['打开灯', '开灯'],
      handler: (params) => console.log('打开灯')
    },
    {
      name: 'closeLight',
      triggers: ['关闭灯', '关灯'],
      handler: (params) => console.log('关闭灯')
    }
  ],

  // 事件监听器(可选,也可以用 sdk.on() 注册)
  events: {
    onReady: () => console.log('SDK 就绪'),
    onVoiceStart: () => console.log('语音开始'),
    onVoiceEnd: (data) => console.log('语音结束,时长:', data.duration),
    onRecognition: (result) => console.log('识别结果:', result.text),
    onCommand: (result) => console.log('指令:', result.command),
    onWake: (result) => console.log('唤醒词:', result.word),
    onError: (error) => console.error('错误:', error.message),
    onStateChange: ({ from, to }) => console.log(`状态: ${from} -> ${to}`)
  }
});

await sdk.init();
await sdk.start();

指令解析器配置

interface CommandParserConfig {
  enablePinyinMatch?: boolean;         // 是否启用拼音匹配,默认 true
  pinyinMatchThreshold?: number;       // 拼音匹配阈值 0-1,默认 0.8
  enableSynonymMatch?: boolean;        // 是否启用同义词匹配,默认 true
  enableKeywordExtraction?: boolean;   // 是否启用关键词提取,默认 true
}

拼音匹配(解决同音字问题)

SDK 内置了拼音匹配功能,可以解决语音识别中的同音字问题。例如:

  • 用户说"向左飞",但语音识别结果是"向佐非"
  • 通过拼音匹配,"向佐非"的拼音 xiang zuo fei 与触发词"向左飞"的拼音相同,可以正确匹配
// 拼音匹配默认启用,无需额外配置
const sdk = new EchoSDK({
  commandParser: {
    enablePinyinMatch: true,        // 启用拼音匹配
    pinyinMatchThreshold: 0.8       // 匹配阈值(0-1,越高越严格)
  },
  commands: [
    {
      name: 'fly_left',
      triggers: ['向左', '往左', '向左飞', '往左飞', '左飞'],
      handler: () => console.log('向左飞')
    },
    {
      name: 'fly_right', 
      triggers: ['向右', '往右', '向右飞', '往右飞', '右飞'],
      handler: () => console.log('向右飞')
    }
  ]
});

// 即使语音识别结果是"向佐飞"、"往佐转",也能正确匹配到"向左飞"指令

拼音匹配 API

// 启用拼音匹配(默认已启用)
sdk.enablePinyinMatch();

// 禁用拼音匹配
sdk.disablePinyinMatch();

// 检查拼音匹配是否启用
const enabled = sdk.isPinyinMatchEnabled();

// 设置拼音匹配阈值(0-1,越高要求越严格)
sdk.setPinyinMatchThreshold(0.8);

同义词匹配(解决表达多样性问题)

SDK 内置了丰富的同义词库,可以识别不同的表达方式。例如:

  • "打开灯" = "开启灯" = "启动灯"
  • "关闭灯" = "关掉灯" = "停止灯"
  • "向左" = "往左" = "左边" = "左转"
const sdk = new EchoSDK({
  commandParser: {
    enableSynonymMatch: true  // 启用同义词匹配(默认启用)
  },
  commands: [
    {
      name: 'open_light',
      triggers: ['打开灯', '开灯'],  // 会自动匹配"开启灯"、"启动灯"等
      handler: () => console.log('打开灯')
    }
  ]
});

同义词匹配 API

// 启用同义词匹配(默认已启用)
sdk.enableSynonymMatch();

// 禁用同义词匹配
sdk.disableSynonymMatch();

// 检查同义词匹配是否启用
const enabled = sdk.isSynonymMatchEnabled();

// 添加自定义同义词组
sdk.addSynonymGroup('飞行', ['飞', '起飞', '飞起来', '飞上去']);

内置同义词库

SDK 内置了以下同义词组:

  • 开关类:打开、开启、启动、关闭、关掉、停止
  • 方向类:向左、往左、左边、向右、往右、右边、向前、向后、向上、向下
  • 调节类:增加、加大、提高、减少、减小、降低
  • 播放控制:播放、暂停、继续、下一首、上一首
  • 设备类:灯、空调、电视、音响
  • 颜色类:红色、绿色、蓝色、黄色、白色
  • 位置类:客厅、卧室、厨房、卫生间

关键词提取(优化短句匹配)

SDK 支持关键词提取功能,可以优化短句匹配准确度。例如:

  • "给我打开灯" → 提取关键词"打开灯"
  • "麻烦帮我关一下灯" → 提取关键词"关灯"
const sdk = new EchoSDK({
  commandParser: {
    enableKeywordExtraction: true  // 启用关键词提取(默认启用)
  }
});

关键词提取 API

// 启用关键词提取(默认已启用)
sdk.enableKeywordExtraction();

// 禁用关键词提取
sdk.disableKeywordExtraction();

// 检查关键词提取是否启用
const enabled = sdk.isKeywordExtractionEnabled();

智能文本标准化

SDK 会自动对语音识别结果进行智能标准化处理:

  1. 口语化表达处理:自动移除"给我"、"帮我"、"麻烦"等口语词
  2. 中文数字转换:自动将"二十三"转换为"23"
  3. 多音字修正:根据上下文自动修正多音字(如:"向佐"→"向左")
  4. 标点符号处理:自动移除标点符号和多余空格

这些处理都是自动进行的,无需额外配置。

声纹识别配置

interface VoiceprintConfig {
  threshold?: number;         // 匹配阈值 0-1,默认 0.88,越高要求越严格
  enabled?: boolean;          // 是否启用声纹识别,默认 false
  minEnrollDuration?: number; // 最小录入时长 (ms),默认 5000
}

唤醒词配置

interface WakeWordConfig {
  enabled?: boolean;          // 是否启用唤醒词,默认 false
  words?: string[];           // 唤醒词列表,默认 ['小云小云']
  sensitivity?: number;       // 灵敏度 0-1,默认 0.6,影响模糊匹配阈值
  response?: string;          // 唤醒后的回复文本,默认 '我在'
  autoStartRecognition?: boolean; // 唤醒后是否自动开始语音识别,默认 true
  commandTimeout?: number;    // 唤醒后等待指令的超时时间 (ms),默认 10000
  fuzzyMatch?: boolean;       // 是否启用模糊匹配,默认 true
  minSimilarity?: number;     // 模糊匹配最小相似度 0-1,默认 0.8
}

TTS 语音合成配置

interface TTSConfig {
  enabled?: boolean;          // 是否启用 TTS,默认 true
  lang?: string;              // 语言,默认 'zh-CN'
  rate?: number;              // 语速 0.1-10,默认 1.0
  pitch?: number;             // 音调 0-2,默认 1.0
  volume?: number;            // 音量 0-1,默认 1.0
  voiceName?: string;         // 首选语音名称
}

🔊 声纹识别

SDK 内置了基于 MFCC 特征的离线声纹识别功能,可以识别不同说话人。

声纹识别特性

  • 完全离线:所有计算在浏览器本地完成
  • 多特征融合:MFCC + 基频 + 频谱质心 + 过零率等
  • 静音过滤:自动过滤静音帧,提高识别准确性
  • 本地存储:声纹数据保存在 localStorage

使用示例

const sdk = new EchoSDK({
  voiceprint: {
    enabled: true,
    threshold: 0.88,          // 匹配阈值,越高越严格
    minEnrollDuration: 5000   // 录入至少需要 5 秒
  }
});

await sdk.init();

// 录入声纹(需要至少 5 秒的音频)
const voiceprint = sdk.enrollVoiceprint('张三', audioData);

// 识别声纹
const result = sdk.recognizeVoiceprint(audioData);
console.log(result);
// { matched: true, name: '张三', similarity: 0.92, isUnknown: false }

// 监听声纹匹配事件
sdk.on('voiceprintMatch', (result) => {
  if (result.matched) {
    console.log(`识别到: ${result.name},相似度: ${(result.similarity * 100).toFixed(1)}%`);
  } else {
    console.log('未识别到已知声纹');
  }
});

// 获取所有已录入的声纹
const voiceprints = sdk.getVoiceprints();

// 删除声纹
sdk.deleteVoiceprint(voiceprintId);

// 清空所有声纹
sdk.clearVoiceprints();

声纹识别 API

// 启用/禁用声纹识别
sdk.enableVoiceprint();
sdk.disableVoiceprint();

// 录入声纹(需要至少 5 秒的音频)
const voiceprint = await sdk.enrollVoiceprint('张三', audioData);

// 添加声纹样本(增强已有声纹的识别准确性)
await sdk.addVoiceprintSample(voiceprintId, audioData);

// 识别声纹
const result = sdk.recognizeVoiceprint(audioData);

// 获取所有已录入的声纹
const voiceprints = sdk.getVoiceprints();

// 获取声纹数量
const count = sdk.getVoiceprintCount();

// 删除声纹
await sdk.deleteVoiceprint(voiceprintId);

// 清空所有声纹
await sdk.clearVoiceprints();

// 设置声纹匹配阈值(0-1,越高要求越严格)
sdk.setVoiceprintThreshold(0.9);

// 获取当前说话人(在语音识别过程中)
const speaker = sdk.getCurrentSpeaker();

// 导出声纹数据(用于备份)
const jsonData = sdk.exportVoiceprints();

// 导入声纹数据
const importedCount = await sdk.importVoiceprints(jsonData);

提高识别准确性的建议

  1. 录入时间要长:至少说 5 秒以上,内容丰富一些
  2. 多次录入:可以多次录入同一个人的声纹来增强准确性
  3. 安静环境:录入和识别时尽量在安静环境下进行
  4. 调整阈值:如果误识别率高,可以提高 threshold 值(如 0.90)

🗣️ 唤醒词检测

SDK 内置了基于 语音识别文本匹配 的唤醒词检测功能,无需录制模板,开箱即用

唤醒词特性

  • 开箱即用:只需配置唤醒词文本,无需录制音频模板
  • 完全离线:基于 Vosk 语音识别,所有计算在浏览器本地完成
  • 精确匹配:检测识别结果是否包含唤醒词
  • 模糊匹配:支持相似度匹配,容忍识别误差
  • TTS 语音回复:唤醒后自动语音回复"我在"
  • 连续指令:支持"小云小云打开灯"这样的连续语音

使用示例

// 创建 SDK 实例并启用唤醒词
const sdk = new EchoSDK({
  debug: true,
  speechRecognition: {
    voskModelPath: '/models/vosk-model-cn.tar.gz'
  },
  wakeWord: {
    enabled: true,            // 启用唤醒词检测
    words: ['小云小云', '你好小云'],  // 唤醒词列表(支持多个)
    sensitivity: 0.6,         // 灵敏度,越高越容易触发
    response: '我在',         // 唤醒后的语音回复
    commandTimeout: 10000,    // 唤醒后 10 秒内等待指令
    fuzzyMatch: true,         // 启用模糊匹配
    minSimilarity: 0.8        // 模糊匹配最小相似度
  },
  tts: {
    enabled: true,
    lang: 'zh-CN',
    rate: 1.0
  },
  commands: [
    {
      name: 'openLight',
      triggers: ['打开灯', '开灯'],
      handler: () => console.log('执行: 打开灯')
    }
  ]
});

// 初始化并启动
await sdk.init();
await sdk.start();

// 监听唤醒事件
sdk.on('wake', (result) => {
  console.log(`唤醒词: ${result.word}, 置信度: ${(result.confidence * 100).toFixed(1)}%`);
  // 唤醒后 SDK 会自动开始监听语音指令
});

// 监听 TTS 事件
sdk.on('speakStart', ({ text }) => console.log('开始朗读:', text));
sdk.on('speakEnd', ({ text }) => console.log('朗读结束:', text));

唤醒词工作流程

  1. 监听模式:SDK 启动后进入唤醒词监听模式,持续进行语音识别
  2. 唤醒检测:检测到唤醒词后,触发 wake 事件,播放语音回复
  3. 指令模式:进入指令等待模式,此时的语音会被解析为指令
  4. 超时休眠:如果超时未收到指令,自动回到唤醒词监听模式

连续指令支持

SDK 支持在唤醒词后直接跟随指令,例如:

  • "小云小云,打开灯" → 唤醒 + 执行"打开灯"指令
  • "小云小云" → 唤醒,等待后续指令
// 用户说:"小云小云打开灯"
// SDK 会:
// 1. 检测到唤醒词"小云小云"
// 2. 触发 wake 事件
// 3. 播放"我在"
// 4. 自动提取并执行"打开灯"指令

唤醒词 API

// 启用/禁用唤醒词检测
sdk.enableWakeWord();
sdk.disableWakeWord();

// 添加唤醒词
sdk.addWakeWord('小助手');

// 移除唤醒词
sdk.removeWakeWord('小助手');

// 获取唤醒词列表
const words = sdk.getWakeWords();

// 设置唤醒词列表
sdk.setWakeWords(['小云小云', '你好小云', '小助手']);

// 设置灵敏度
sdk.setWakeWordSensitivity(0.8);

// 设置是否启用模糊匹配
sdk.setWakeWordFuzzyMatch(true);

// 检查唤醒状态
const isAwake = sdk.getIsAwake();

// 手动设置唤醒状态
sdk.setAwake(true);  // 手动唤醒(跳过唤醒词检测)
sdk.setAwake(false); // 手动休眠

提高唤醒词识别准确性的建议

  1. 唤醒词选择:选择 2-4 个音节的词语效果最佳,如"小云小云"、"你好小云"
  2. 避免常见词:避免使用日常对话中常见的词语,减少误触发
  3. 调整灵敏度:如果误触发多,降低灵敏度;如果难以触发,提高灵敏度
  4. 安静环境:在安静环境下使用效果更好
  5. 清晰发音:说唤醒词时发音清晰,语速适中

📢 TTS 语音合成

SDK 内置了基于 Web Speech API 的 TTS 语音合成功能。

TTS 使用示例

const sdk = new EchoSDK({
  tts: {
    enabled: true,
    lang: 'zh-CN',
    rate: 1.0,      // 语速
    pitch: 1.0,     // 音调
    volume: 1.0     // 音量
  }
});

await sdk.init();

// 朗读文本
await sdk.speak('你好,我是小云');

// 停止朗读
sdk.stopSpeak();

// 检查是否正在朗读
const isSpeaking = sdk.getIsSpeaking();

// 调整参数
sdk.setTTSRate(1.2);    // 加快语速
sdk.setTTSPitch(1.1);   // 提高音调
sdk.setTTSVolume(0.8);  // 降低音量

// 获取可用语音列表
const voices = sdk.getTTSVoices();
console.log('可用语音:', voices.map(v => v.name));

// 设置语音
sdk.setTTSVoice('Microsoft Xiaoxiao');

// 启用/禁用 TTS
sdk.enableTTS();
sdk.disableTTS();

🎮 指令管理

SDK 支持动态添加、移除和管理指令。

指令管理 API

// 添加单个指令
sdk.addCommand({
  name: 'turnOff',
  triggers: ['关闭', '关掉'],
  handler: (params) => console.log('关闭设备')
});

// 批量添加指令
sdk.addCommands([
  {
    name: 'volumeUp',
    triggers: ['大声点', '音量加'],
    handler: () => console.log('音量增加')
  },
  {
    name: 'volumeDown',
    triggers: ['小声点', '音量减'],
    handler: () => console.log('音量减少')
  }
]);

// 移除指令
sdk.removeCommand('turnOff');

// 清空指令上下文(重置上下文状态)
sdk.clearContext();

⚙️ 配置管理

SDK 支持在运行时获取和更新配置。

配置管理 API

// 获取当前配置
const config = sdk.getConfig();
console.log('当前配置:', config);

// 更新配置(支持部分更新)
sdk.updateConfig({
  debug: true,
  vad: {
    silenceThreshold: 600
  },
  tts: {
    rate: 1.2
  }
});

📡 事件

SDK 使用事件驱动模式,支持注册和移除事件监听器。

事件监听 API

// 注册事件监听器
sdk.on('recognition', (result) => {
  console.log('识别结果:', result.text);
});

// 移除事件监听器
const handler = (result) => console.log(result);
sdk.on('command', handler);
sdk.off('command', handler);  // 移除指定的监听器

事件列表

| 事件名 | 描述 | 回调参数 | |-------|------|---------| | voiceStart | 语音开始 | 无 | | voiceEnd | 语音结束 | { duration: number, audioData?: Float32Array } | | recognition | 语音识别结果 | { text: string, confidence: number, isFinal: boolean } | | command | 指令识别 | { command: string, params: object, confidence: number } | | voiceprintMatch | 声纹匹配结果 | { matched: boolean, name: string, similarity: number, isUnknown: boolean } | | wake | 唤醒词检测 | { word: string, confidence: number, timestamp: number } | | speakStart | TTS 开始朗读 | { text: string } | | speakEnd | TTS 朗读结束 | { text: string } | | error | 错误 | { type: string, message: string } | | ready | SDK 就绪 | 无 | | stopped | SDK 停止 | 无 | | stateChange | 状态变化 | { from: SDKState, to: SDKState } |

🔍 语音识别状态检测

SDK 提供了多个方法来检测当前的语音识别状态,方便在 UI 中显示状态指示器。

状态检测 API

// 检查当前是否正在进行语音识别
const isRecognizing = sdk.getIsRecognizing();

// 检查当前是否检测到语音活动(用户正在讲话)
const isVoiceActive = sdk.getIsVoiceActive();

// 检查当前是否正在监听(麦克风已开启)
const isListening = sdk.getIsListening();

// 获取当前 SDK 状态
const state = sdk.getState();
// 可能的值: 'idle' | 'initializing' | 'ready' | 'running' | 'listening' | 'processing' | 'stopped' | 'error'

// 获取完整状态信息
const status = sdk.getStatus();
console.log(status);
// {
//   state: 'listening',
//   isInitialized: true,
//   isListening: true,
//   isRecognizing: false,
//   isVoiceActive: false,
//   isAwake: false,
//   isPinyinMatchEnabled: true,
//   ...
// }

状态变化监听

// 监听状态变化事件
sdk.on('stateChange', ({ from, to }) => {
  console.log(`状态从 ${from} 变为 ${to}`);
  
  // 更新 UI 状态指示器
  if (to === 'listening') {
    showListeningIndicator();
  } else if (to === 'running') {
    showRecognizingIndicator();
  }
});

在 Vue/React 中使用示例

// Vue 3 Composition API 示例
import { ref, onMounted, onUnmounted } from 'vue';
import EchoSDK from 'echo-voice-sdk';

export function useEchoVoice() {
  const sdk = ref(null);
  const isListening = ref(false);
  const isRecognizing = ref(false);
  const isVoiceActive = ref(false);
  
  // 定时更新状态
  let statusInterval = null;
  
  onMounted(async () => {
    sdk.value = new EchoSDK({ /* 配置 */ });
    await sdk.value.init();
    
    // 每 100ms 更新状态
    statusInterval = setInterval(() => {
      if (sdk.value) {
        isListening.value = sdk.value.getIsListening();
        isRecognizing.value = sdk.value.getIsRecognizing();
        isVoiceActive.value = sdk.value.getIsVoiceActive();
      }
    }, 100);
  });
  
  onUnmounted(() => {
    if (statusInterval) clearInterval(statusInterval);
    sdk.value?.destroy();
  });
  
  return {
    sdk,
    isListening,
    isRecognizing,
    isVoiceActive
  };
}

🎤 麦克风权限管理

SDK 提供了完整的麦克风权限检测和引导功能,帮助处理用户拒绝权限的情况。

自动权限检查

SDK 在初始化时会自动检查权限,如果检测到权限被拒绝,会自动弹出引导弹窗:

const sdk = new EchoSDK({
  speechRecognition: {
    voskModelPath: '/models/vosk-model-cn.tar.gz'
  }
});

// 初始化时自动检查权限
await sdk.init(); // 如果权限被拒绝,会自动弹出引导弹窗

手动权限管理

你也可以在初始化前手动检查和请求权限:

const sdk = new EchoSDK({ /* 配置 */ });

// 1. 检查所有权限(麦克风 + TTS)
const permissionResult = await sdk.checkAllPermissions();
console.log('麦克风权限:', permissionResult.microphone); // 'granted' | 'denied' | 'prompt'
console.log('TTS 权限:', permissionResult.tts);
console.log('所有权限已授权:', permissionResult.allGranted);
console.log('有权限被拒绝:', permissionResult.hasDenied);

// 2. 请求权限(带弹窗提示)
if (!permissionResult.allGranted) {
  const result = await sdk.requestPermissions({
    showDialog: true,
    dialogTitle: '需要授权',
    dialogMessage: '为了使用语音功能,需要您授权麦克风权限',
    confirmText: '授权',
    cancelText: '取消'
  });
  
  if (result.allGranted) {
    console.log('权限已授予');
    await sdk.init();
  } else {
    console.log('权限被拒绝');
  }
}

自定义权限弹窗

你可以使用自定义弹窗替代默认弹窗:

await sdk.requestPermissions({
  showDialog: true,
  customDialog: async (config) => {
    // config 包含:type, permission, title, message, steps, browser, confirmText, cancelText
    
    if (config.type === 'request') {
      // 显示请求权限的弹窗
      return await showMyCustomRequestDialog(config);
    } else if (config.type === 'denied') {
      // 显示权限被拒绝的引导弹窗
      await showMyCustomDeniedDialog(config);
      return false;
    }
  }
});

Vue 3 示例

<template>
  <div>
    <div v-if="permissionStatus === 'prompt'">
      <button @click="handleRequestPermission">授权麦克风</button>
    </div>
    <div v-else-if="permissionStatus === 'denied'">
      <p>麦克风权限被拒绝,请按以下步骤手动开启:</p>
      <ol>
        <li v-for="(step, index) in guidanceSteps" :key="index">{{ step }}</li>
      </ol>
    </div>
    <div v-else-if="permissionStatus === 'granted'">
      <button @click="handleStart">开始使用</button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import EchoSDK from 'echo-voice-sdk';

const sdk = ref(null);
const permissionStatus = ref('unknown');
const guidanceSteps = ref([]);

onMounted(async () => {
  sdk.value = new EchoSDK({
    speechRecognition: {
      voskModelPath: '/models/vosk-model-cn.tar.gz'
    }
  });

  // 检查权限
  const result = await sdk.value.checkAllPermissions();
  permissionStatus.value = result.microphone;
  
  // 监听权限变化
  sdk.value.onPermissionChange((result) => {
    permissionStatus.value = result.microphone;
  });
});

const handleRequestPermission = async () => {
  const result = await sdk.value.requestPermissions({
    showDialog: true
  });
  
  if (result.hasDenied) {
    // 获取引导步骤
    const guidance = await sdk.value.getMicrophonePermissionGuidance();
    if (guidance.guidance) {
      guidanceSteps.value = guidance.guidance.steps;
    }
  }
};

const handleStart = async () => {
  await sdk.value.init();
  await sdk.value.start();
};
</script>

React 示例

import { useState, useEffect } from 'react';
import EchoSDK from 'echo-voice-sdk';

function App() {
  const [sdk, setSdk] = useState(null);
  const [permissionStatus, setPermissionStatus] = useState('unknown');
  const [guidanceSteps, setGuidanceSteps] = useState([]);

  useEffect(() => {
    const instance = new EchoSDK({
      speechRecognition: {
        voskModelPath: '/models/vosk-model-cn.tar.gz'
      }
    });

    // 检查权限
    instance.checkAllPermissions().then(result => {
      setPermissionStatus(result.microphone);
    });

    // 监听权限变化
    instance.onPermissionChange(result => {
      setPermissionStatus(result.microphone);
    });

    setSdk(instance);
  }, []);

  const handleRequestPermission = async () => {
    const result = await sdk.requestPermissions({
      showDialog: true
    });
    
    if (result.hasDenied) {
      const guidance = await sdk.getMicrophonePermissionGuidance();
      if (guidance.guidance) {
        setGuidanceSteps(guidance.guidance.steps);
      }
    }
  };

  const handleStart = async () => {
    await sdk.init();
    await sdk.start();
  };

  if (permissionStatus === 'prompt') {
    return <button onClick={handleRequestPermission}>授权麦克风</button>;
  }

  if (permissionStatus === 'denied') {
    return (
      <div>
        <p>麦克风权限被拒绝,请按以下步骤手动开启:</p>
        <ol>
          {guidanceSteps.map((step, index) => (
            <li key={index}>{step}</li>
          ))}
        </ol>
      </div>
    );
  }

  if (permissionStatus === 'granted') {
    return <button onClick={handleStart}>开始使用</button>;
  }

  return <div>检查权限中...</div>;
}

权限状态类型

enum PermissionStatus {
  GRANTED = 'granted',   // 已授权
  DENIED = 'denied',     // 已拒绝
  PROMPT = 'prompt',     // 待询问(用户尚未做出选择)
  UNKNOWN = 'unknown'    // 未知(浏览器不支持权限查询)
}

权限检查结果接口

interface PermissionCheckResult {
  microphone: PermissionStatus;  // 麦克风权限状态
  tts: PermissionStatus;         // TTS 权限状态
  allGranted: boolean;           // 是否所有权限都已授权
  hasDenied: boolean;            // 是否有权限被拒绝
  deniedPermissions: string[];   // 拒绝的权限列表
}

权限相关 API

// 检查所有权限
const result = await sdk.checkAllPermissions();

// 请求权限(带弹窗)
const result = await sdk.requestPermissions(config);

// 监听权限变化
sdk.onPermissionChange((result) => {
  console.log('权限状态变化:', result);
});

// 获取当前权限状态
const status = sdk.getPermissionStatus();
console.log('麦克风:', status.microphone);
console.log('TTS:', status.tts);

// 获取麦克风权限引导信息
const guidance = await sdk.getMicrophonePermissionGuidance();
if (guidance.guidance) {
  console.log('浏览器:', guidance.guidance.browser);
  console.log('操作步骤:', guidance.guidance.steps);
}

🎤 麦克风权限管理(旧 API,仍然可用)

SDK 提供了完整的麦克风权限检测和引导功能,帮助处理用户拒绝权限的情况。

权限状态类型

enum MicrophonePermissionState {
  GRANTED = 'granted',   // 已授权
  DENIED = 'denied',     // 已拒绝
  PROMPT = 'prompt',     // 待询问(用户尚未做出选择)
  UNKNOWN = 'unknown'    // 未知(浏览器不支持权限查询)
}

权限信息接口

interface MicrophonePermissionInfo {
  state: MicrophonePermissionState;  // 权限状态
  canRequest: boolean;               // 是否可以请求权限
  isDenied: boolean;                 // 是否已被拒绝
  isGranted: boolean;                // 是否已授权
  guidance?: MicrophonePermissionGuidance;  // 引导信息(权限被拒绝时提供)
}

interface MicrophonePermissionGuidance {
  browser: string;      // 浏览器类型
  title: string;        // 引导标题
  steps: string[];      // 操作步骤
  settingsUrl?: string; // 设置页面 URL(部分浏览器支持)
}

权限相关 API

// 检查麦克风权限状态
const permissionInfo = await sdk.checkMicrophonePermission();
console.log('权限状态:', permissionInfo.state);
console.log('是否已授权:', permissionInfo.isGranted);
console.log('是否已拒绝:', permissionInfo.isDenied);
console.log('是否可请求:', permissionInfo.canRequest);

// 请求麦克风权限(如果之前被拒绝,不会弹出授权窗口)
const result = await sdk.requestMicrophonePermission();
if (result.isDenied) {
  // 显示引导信息
  console.log(result.guidance.steps.join('\n'));
}

// 监听权限状态变化
sdk.onMicrophonePermissionChange((state) => {
  if (state === 'granted') {
    console.log('权限已授予,可以启动');
    sdk.start();
  } else if (state === 'denied') {
    console.log('权限被拒绝');
  }
});

// 获取权限引导信息
const info = await sdk.getMicrophonePermissionGuidance();
if (info.guidance) {
  console.log(`${info.guidance.browser} 浏览器操作指引:`);
  info.guidance.steps.forEach(step => console.log(step));
}

完整的权限处理示例

async function startWithPermissionCheck() {
  const sdk = new EchoSDK({ debug: true });
  
  // 1. 先检查权限状态
  const permissionInfo = await sdk.checkMicrophonePermission();
  
  if (permissionInfo.isDenied) {
    // 2. 权限已被拒绝,显示引导
    const guidance = permissionInfo.guidance;
    showPermissionGuideModal({
      title: guidance.title,
      steps: guidance.steps,
      browser: guidance.browser
    });
    return;
  }
  
  // 3. 监听权限变化(用户可能在设置中修改)
  sdk.onMicrophonePermissionChange((state) => {
    if (state === 'granted') {
      hidePermissionGuideModal();
      sdk.start();
    }
  });
  
  // 4. 监听错误事件
  sdk.on('error', (error) => {
    if (error.type === 'permission_error') {
      const info = error.originalError?.permissionInfo;
      if (info?.guidance) {
        showPermissionGuideModal({
          title: info.guidance.title,
          steps: info.guidance.steps,
          browser: info.guidance.browser
        });
      }
    }
  });
  
  // 5. 初始化并启动
  try {
    await sdk.init();
    await sdk.start();
  } catch (error) {
    if (error.permissionInfo) {
      // 权限错误已在 error 事件中处理
    } else {
      console.error('启动失败:', error);
    }
  }
}

🎯 Vosk 模型

本 SDK 使用 Vosk 进行离线语音识别,中文模型已内置在 npm 包中,无需单独下载。

使用内置模型

通过 npm 安装后,模型文件位于 node_modules/echo-voice-sdk/models/vosk-model-cn.tar.gz

你需要将模型文件复制到你的静态资源目录(如 public/models/),然后在配置中指定路径:

const sdk = new EchoSDK({
  speechRecognition: {
    voskModelPath: '/models/vosk-model-cn.tar.gz'
  }
});

Vite 项目配置示例

// vite.config.js
import { defineConfig } from 'vite';
import { copyFileSync, mkdirSync, existsSync } from 'fs';

export default defineConfig({
  plugins: [{
    name: 'copy-vosk-model',
    buildStart() {
      const modelSrc = 'node_modules/echo-voice-sdk/models/vosk-model-cn.tar.gz';
      const modelDest = 'public/models/vosk-model-cn.tar.gz';
      if (!existsSync('public/models')) {
        mkdirSync('public/models', { recursive: true });
      }
      if (existsSync(modelSrc)) {
        copyFileSync(modelSrc, modelDest);
      }
    }
  }]
});

Webpack 项目配置示例

// webpack.config.js
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [
        { 
          from: 'node_modules/echo-voice-sdk/models/vosk-model-cn.tar.gz', 
          to: 'models/vosk-model-cn.tar.gz' 
        }
      ]
    })
  ]
};

使用 CDN(可选)

如果不想在项目中包含模型文件,也可以使用 CDN:

const sdk = new EchoSDK({
  speechRecognition: {
    voskModelPath: 'https://your-cdn.com/vosk-model-cn.tar.gz'
  }
});

⚠️ 注意:模型文件约 42MB,首次加载需要一定时间。建议配置浏览器缓存以提升后续加载速度。

🛠️ 开发

# 安装依赖
npm install

# 开发模式
npm run dev

# 构建
npm run build

# 运行 Demo
npm run demo

📁 项目结构

YZ_EchoSDK/
├── src/
│   ├── core/
│   │   ├── EchoSDK.ts        # SDK 主类
│   │   └── EventManager.ts   # 事件管理器
│   ├── modules/
│   │   ├── AudioProcessor.ts # 音频处理器
│   │   ├── VADDetector.ts    # VAD 检测器
│   │   ├── SpeechRecognizer.ts # Vosk 语音识别
│   │   ├── CommandParser.ts  # 指令解析器(含拼音匹配)
│   │   ├── VoiceprintRecognizer.ts # 声纹识别器
│   │   ├── WakeWordDetector.ts # 唤醒词检测器
│   │   ├── TTSSpeaker.ts     # TTS 语音合成
│   │   └── PermissionManager.ts # 权限管理器
│   ├── utils/
│   │   └── PinyinUtils.ts    # 拼音工具类(pinyin-pro)
│   ├── types/
│   │   └── index.ts          # 类型定义
│   └── index.ts              # 入口文件
├── modules/
│   ├── vosk-model-small-cn-0.22/  # Vosk 中文模型
│   └── vosk-model-cn.tar.gz       # 打包后的模型
├── examples/
│   ├── demo.html             # 基础语音识别演示(Vosk + 指令 + 声纹 + 唤醒词)
│   ├── demo-sdk.html         # SDK 完整功能演示(含拼音匹配)
│   ├── demo-voiceprint.html  # 声纹识别专项演示(录制、注册、识别)
│   ├── demo-permission.html  # 权限管理演示(自动检查、请求、引导)
│   ├── demo-accuracy.html    # 识别准确度优化演示
│   ├── test-tts-fix.html     # TTS 修复测试页面(新增)
│   └── server-example/       # 服务端示例
├── dist/                     # 构建输出
└── package.json

🐛 常见问题

1. TTS 语音播放一次后就不播放了

问题描述:在浏览器中使用 TTS 语音合成,播放一次后再次调用或刷新页面都无法播放。

原因:这是浏览器 speechSynthesis API 的已知 bug,特别是在 Chrome 浏览器中。如果不正确处理状态,语音合成会"卡住"。

解决方案:SDK v1.5.1 已修复此问题,主要改进:

  1. 播放前清理状态:在每次播放前调用 cancel() 清理浏览器状态
  2. 延迟调用:使用 setTimeout 延迟调用 speak(),避免竞态条件
  3. 完全清理:在 stop()destroy() 中确保完全清理状态

如果仍有问题,可以尝试:

// 手动重置 speechSynthesis
window.speechSynthesis.cancel();

// 然后再调用 SDK 的 speak 方法
await sdk.speak('你好');

2. 麦克风权限被拒绝

问题描述:用户拒绝麦克风权限后,无法再次请求。

解决方案

  1. SDK 会自动检测权限状态并显示引导弹窗
  2. 用户需要在浏览器设置中手动开启权限
  3. 使用 checkMicrophonePermission() 检查权限状态
  4. 使用 requestPermissions() 请求权限(带引导)
// 检查权限
const permissionInfo = await sdk.checkMicrophonePermission();
if (permissionInfo.state === 'denied') {
  console.log('权限被拒绝,请按以下步骤操作:');
  console.log(permissionInfo.guidance.steps);
}

// 请求权限(带弹窗引导)
await sdk.requestPermissions({
  showDialog: true,
  dialogTitle: '需要麦克风权限',
  dialogMessage: '请允许使用麦克风以启用语音功能'
});

3. Vosk 模型加载失败

问题描述:提示 "Failed to load model" 或模型加载超时。

解决方案

  1. 确保模型文件路径正确(默认 /models/vosk-model-cn.tar.gz
  2. 检查模型文件是否存在且完整(约 42MB)
  3. 检查服务器 MIME 类型配置(应为 application/gzip
  4. 首次加载需要 10-30 秒,请耐心等待
// 自定义模型路径
const sdk = new EchoSDK({
  speechRecognition: {
    voskModelPath: '/your-path/vosk-model-cn.tar.gz'
  }
});

4. 识别准确度不高

问题描述:语音识别结果不准确,或指令匹配失败。

解决方案

  1. 启用拼音匹配:解决同音字问题
  2. 启用同义词匹配:识别同义词表达
  3. 启用关键词提取:优化短句匹配
  4. 调整匹配阈值:降低阈值提高匹配成功率
// 启用所有优化功能
sdk.enablePinyinMatch();
sdk.enableSynonymMatch();
sdk.enableKeywordExtraction();

// 调整拼音匹配阈值(默认 0.7)
sdk.setPinyinMatchThreshold(0.6);

// 添加自定义同义词
sdk.addSynonymGroup('飞行', ['飞', '起飞', '飞起来']);

详细优化指南请查看:识别准确度优化文档

5. 唤醒词检测不灵敏

问题描述:说了唤醒词但系统没有响应。

解决方案

  1. 调整灵敏度:降低灵敏度阈值
  2. 启用模糊匹配:允许一定的识别误差
  3. 添加多个唤醒词:增加唤醒成功率
// 降低灵敏度阈值(0-1,越小越灵敏)
sdk.setWakeWordSensitivity(0.5);

// 启用模糊匹配
sdk.setWakeWordFuzzyMatch(true);

// 添加多个唤醒词
sdk.setWakeWords(['小云小云', '你好小云', '嘿小云']);

6. 声纹识别不准确

问题描述:声纹识别匹配失败或误识别。

解决方案

  1. 录入时长充足:确保录入时长至少 3-5 秒
  2. 环境安静:录入时保持环境安静,减少噪音
  3. 多次录入:为同一用户录入多个样本
  4. 调整阈值:根据实际情况调整匹配阈值
// 调整声纹匹配阈值(0-1,越高要求越严格)
sdk.setVoiceprintThreshold(0.8);

// 为用户添加多个声纹样本
await sdk.addVoiceprintSample(voiceprintId, audioData);

7. 浏览器兼容性问题

支持的浏览器

  • ✅ Chrome 90+
  • ✅ Edge 90+
  • ✅ Safari 14+
  • ✅ Firefox 88+
  • ❌ IE(不支持)

注意事项

  1. Safari 对 Web Speech API 支持有限,TTS 功能可能受限
  2. Firefox 的 speechSynthesis 实现与 Chrome 略有不同
  3. 移动端浏览器支持情况因系统而异

8. 性能优化建议

问题描述:SDK 运行时占用资源较多。

优化建议

  1. 按需启用功能:不需要的功能可以禁用
  2. 调整缓冲区大小:减小 bufferSize 可降低延迟但增加 CPU 占用
  3. 使用较小的模型:如果对准确度要求不高,可使用更小的 Vosk 模型
  4. 限制声纹数量:声纹识别的计算量与声纹数量成正比
const sdk = new EchoSDK({
  bufferSize: 2048, // 减小缓冲区(默认 4096)
  voiceprint: {
    enabled: false // 不需要时禁用声纹识别
  },
  wakeWord: {
    enabled: false // 不需要时禁用唤醒词
  }
});

📄 许可证

MIT License