SenseVoice-small WebUI使用:Chrome扩展程序集成语音输入功能
本文介绍了如何在星图GPU平台上自动化部署sensevoice-small-轻量级多任务语音模型的ONNX量化版WebUI V1.0镜像,并利用其构建Chrome扩展程序,实现网页端的语音输入与实时字幕生成功能,显著提升在线文档编辑、视频学习等场景的效率。
SenseVoice-small WebUI使用:Chrome扩展程序集成语音输入功能
你是不是也遇到过这样的场景?在电脑上写文档、做笔记,或者需要快速记录一些想法时,打字速度跟不上思维,手忙脚乱。或者,在观看外语视频、参加线上会议时,希望能实时看到字幕,但又不想安装复杂的软件。
今天,我要分享一个超级实用的技巧:如何将SenseVoice-small这个强大的语音识别工具,通过Chrome扩展程序的方式,变成你浏览器里的“语音输入助手”。这不仅仅是简单的语音转文字,而是让你在任何网页输入框里,都能用说话代替打字,还能实时生成视频字幕,彻底解放你的双手。
1. 为什么需要浏览器语音输入?
在介绍具体方法之前,我们先聊聊为什么这个功能如此重要。
想象一下这些日常场景:
- 写邮件或文档:对着麦克风说,文字自动出现在编辑框里
- 在线搜索:直接说出你想找的内容,不用再费力打字
- 填写在线表单:口述信息,自动填充到各个字段
- 看视频学习:实时生成字幕,特别是外语内容
- 会议记录:自动转录线上会议内容
传统的解决方案要么需要安装庞大的软件,要么必须联网使用,隐私和安全都成问题。而SenseVoice-small的本地部署特性,加上Chrome扩展的便捷性,正好解决了这些痛点。
2. SenseVoice-small:你的本地语音识别引擎
SenseVoice-small是一个轻量级的多任务语音模型,我使用的是它的ONNX量化版WebUI V1.0。简单来说,它有以下几个核心优势:
2.1 完全本地运行,保护隐私
所有语音处理都在你的设备上完成,音频数据不会上传到任何服务器。这对于处理敏感信息(如医疗记录、财务讨论、内部会议)来说至关重要。
2.2 支持50多种语言
不仅仅是中文和英文,还支持日语、韩语、粤语、俄语、西班牙语等全球主流语言。更厉害的是,它能自动检测语言类型,你不需要手动切换。
2.3 轻量高效,资源占用少
经过ONNX量化和优化后,这个模型可以在没有独立GPU的普通电脑上流畅运行,甚至在一些性能较好的手机和平板上也能使用。
2.4 多功能集成
除了基本的语音转文字,还能识别说话人的情感状态(开心、悲伤、愤怒等),并进行智能文本转换(比如把“一百二十”自动转成“120”)。
3. 准备工作:部署SenseVoice-small WebUI
在开始制作Chrome扩展之前,你需要先让SenseVoice-small在本地运行起来。
3.1 基础环境要求
- 操作系统:Windows 10/11,macOS 10.15+,或Linux(Ubuntu 20.04+推荐)
- 内存:至少8GB RAM(16GB更佳)
- 存储空间:2GB可用空间用于模型文件
- Python:3.8-3.11版本
3.2 快速部署步骤
如果你已经按照官方文档部署好了WebUI服务,可以跳过这一步。如果还没有,这里是最简化的部署流程:
# 1. 克隆项目代码
git clone https://github.com/your-repo/sensevoice-small-webui.git
cd sensevoice-small-webui
# 2. 创建Python虚拟环境(推荐)
python -m venv venv
source venv/bin/activate # Linux/macOS
# 或 venv\Scripts\activate # Windows
# 3. 安装依赖
pip install -r requirements.txt
# 4. 下载模型文件(如果还没有)
# 模型会自动下载,如果需要手动下载:
# 将模型文件放在 ./models/ 目录下
# 5. 启动WebUI服务
python webui.py
服务启动后,在浏览器中访问 http://localhost:7860,你应该能看到SenseVoice的Web界面。
3.3 验证服务正常运行
打开WebUI后,尝试上传一个音频文件或使用麦克风录音测试。如果一切正常,你会看到识别结果。记下这个本地服务的地址(通常是 http://localhost:7860),我们稍后会用到。
4. 创建Chrome扩展程序
现在进入核心部分:创建一个Chrome扩展,让它能够调用本地的SenseVoice服务。
4.1 扩展程序项目结构
首先创建一个新的文件夹,比如 sensevoice-extension,然后建立以下文件结构:
sensevoice-extension/
├── manifest.json # 扩展配置文件
├── popup.html # 弹出窗口界面
├── popup.js # 弹出窗口逻辑
├── background.js # 后台服务脚本
├── content.js # 页面注入脚本
└── icons/ # 图标文件夹
├── icon16.png
├── icon48.png
└── icon128.png
4.2 关键文件详解
manifest.json - 扩展的“身份证”
{
"manifest_version": 3,
"name": "SenseVoice 语音输入助手",
"version": "1.0.0",
"description": "使用本地SenseVoice服务实现语音输入和实时字幕",
"permissions": [
"activeTab",
"storage",
"scripting"
],
"host_permissions": [
"http://localhost:7860/*"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
}
}
popup.html - 扩展弹出界面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
width: 300px;
padding: 15px;
font-family: Arial, sans-serif;
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
}
.connected { background: #d4edda; color: #155724; }
.disconnected { background: #f8d7da; color: #721c24; }
button {
width: 100%;
padding: 10px;
margin: 5px 0;
background: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover { background: #0056b3; }
button:disabled { background: #cccccc; }
.language-select {
width: 100%;
padding: 8px;
margin: 10px 0;
}
</style>
</head>
<body>
<h3>SenseVoice 语音输入</h3>
<div id="status" class="status disconnected">
正在连接服务...
</div>
<select id="language" class="language-select">
<option value="auto">自动检测语言</option>
<option value="zh">中文</option>
<option value="en">英文</option>
<option value="yue">粤语</option>
<option value="ja">日语</option>
<option value="ko">韩语</option>
</select>
<button id="startRecord">🎤 开始语音输入</button>
<button id="stopRecord" disabled>⏹️ 停止录音</button>
<button id="startSubtitle">📺 开启实时字幕</button>
<button id="stopSubtitle" disabled>🛑 关闭字幕</button>
<div style="margin-top: 15px; font-size: 12px; color: #666;">
<label>
<input type="checkbox" id="autoPunctuation" checked>
自动添加标点
</label>
</div>
<script src="popup.js"></script>
</body>
</html>
popup.js - 弹出窗口逻辑
// 连接状态检查
async function checkConnection() {
try {
const response = await fetch('http://localhost:7860/');
if (response.ok) {
document.getElementById('status').className = 'status connected';
document.getElementById('status').textContent = '✅ 服务连接正常';
return true;
}
} catch (error) {
document.getElementById('status').className = 'status disconnected';
document.getElementById('status').textContent = '❌ 服务未连接';
return false;
}
}
// 获取当前标签页
async function getCurrentTab() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
return tab;
}
// 开始语音输入
document.getElementById('startRecord').addEventListener('click', async () => {
const tab = await getCurrentTab();
const language = document.getElementById('language').value;
const autoPunctuation = document.getElementById('autoPunctuation').checked;
chrome.tabs.sendMessage(tab.id, {
action: 'startRecording',
language: language,
autoPunctuation: autoPunctuation
});
document.getElementById('startRecord').disabled = true;
document.getElementById('stopRecord').disabled = false;
});
// 停止录音
document.getElementById('stopRecord').addEventListener('click', async () => {
const tab = await getCurrentTab();
chrome.tabs.sendMessage(tab.id, { action: 'stopRecording' });
document.getElementById('startRecord').disabled = false;
document.getElementById('stopRecord').disabled = true;
});
// 开启实时字幕
document.getElementById('startSubtitle').addEventListener('click', async () => {
const tab = await getCurrentTab();
const language = document.getElementById('language').value;
chrome.tabs.sendMessage(tab.id, {
action: 'startSubtitle',
language: language
});
document.getElementById('startSubtitle').disabled = true;
document.getElementById('stopSubtitle').disabled = false;
});
// 关闭字幕
document.getElementById('stopSubtitle').addEventListener('click', async () => {
const tab = await getCurrentTab();
chrome.tabs.sendMessage(tab.id, { action: 'stopSubtitle' });
document.getElementById('startSubtitle').disabled = false;
document.getElementById('stopSubtitle').disabled = true;
});
// 初始化检查
checkConnection();
content.js - 页面内容脚本
// 语音识别状态
let isRecording = false;
let mediaRecorder = null;
let audioChunks = [];
let subtitleActive = false;
let subtitleElement = null;
// 创建字幕显示元素
function createSubtitleElement() {
const subtitle = document.createElement('div');
subtitle.id = 'sensevoice-subtitle';
subtitle.style.cssText = `
position: fixed;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 15px 25px;
border-radius: 10px;
font-size: 18px;
max-width: 80%;
text-align: center;
z-index: 10000;
backdrop-filter: blur(10px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
`;
document.body.appendChild(subtitle);
return subtitle;
}
// 发送音频到SenseVoice服务
async function sendAudioToSenseVoice(audioBlob, language, endpoint = 'transcribe') {
const formData = new FormData();
formData.append('audio', audioBlob, 'recording.webm');
formData.append('language', language);
try {
const response = await fetch(`http://localhost:7860/${endpoint}`, {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('识别失败:', error);
return { text: '识别失败,请检查服务连接', error: error.message };
}
}
// 开始录音
async function startRecording(language, autoPunctuation) {
if (isRecording) return;
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
audioChunks = [];
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
audioChunks.push(event.data);
}
};
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
// 获取当前聚焦的输入元素
const activeElement = document.activeElement;
const isInput = activeElement.tagName === 'INPUT' ||
activeElement.tagName === 'TEXTAREA' ||
activeElement.isContentEditable;
if (isInput) {
// 语音输入模式:将识别结果插入到输入框
const result = await sendAudioToSenseVoice(audioBlob, language);
if (result.text) {
const textToInsert = autoPunctuation ?
addPunctuation(result.text) : result.text;
if (activeElement.isContentEditable) {
// 可编辑元素(如富文本编辑器)
document.execCommand('insertText', false, textToInsert);
} else {
// 普通输入框或文本域
const start = activeElement.selectionStart;
const end = activeElement.selectionEnd;
const text = activeElement.value;
activeElement.value = text.substring(0, start) +
textToInsert +
text.substring(end);
// 移动光标到插入文本后
const newPos = start + textToInsert.length;
activeElement.selectionStart = activeElement.selectionEnd = newPos;
}
// 触发输入事件,让页面知道内容已更改
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
activeElement.dispatchEvent(new Event('change', { bubbles: true }));
}
} else if (subtitleActive && subtitleElement) {
// 字幕模式:显示识别结果
const result = await sendAudioToSenseVoice(audioBlob, language);
if (result.text) {
subtitleElement.textContent = result.text;
// 3秒后自动清除字幕
setTimeout(() => {
if (subtitleElement.textContent === result.text) {
subtitleElement.textContent = '';
}
}, 3000);
}
}
// 清理资源
stream.getTracks().forEach(track => track.stop());
};
mediaRecorder.start(1000); // 每1秒收集一次数据
isRecording = true;
// 如果正在输入框,显示录音提示
if (document.activeElement.tagName === 'INPUT' ||
document.activeElement.tagName === 'TEXTAREA') {
showRecordingIndicator();
}
} catch (error) {
console.error('录音失败:', error);
alert('无法访问麦克风,请检查权限设置');
}
}
// 停止录音
function stopRecording() {
if (mediaRecorder && isRecording) {
mediaRecorder.stop();
isRecording = false;
hideRecordingIndicator();
}
}
// 显示录音指示器
function showRecordingIndicator() {
let indicator = document.getElementById('recording-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'recording-indicator';
indicator.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #ff4757;
color: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
z-index: 10000;
animation: pulse 1.5s infinite;
`;
document.body.appendChild(indicator);
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
`;
document.head.appendChild(style);
}
indicator.textContent = '🎤 录音中...';
indicator.style.display = 'block';
}
// 隐藏录音指示器
function hideRecordingIndicator() {
const indicator = document.getElementById('recording-indicator');
if (indicator) {
indicator.style.display = 'none';
}
}
// 智能添加标点
function addPunctuation(text) {
// 简单的标点添加逻辑,可以根据需要扩展
let result = text.trim();
// 在疑问词后添加问号
const questionWords = ['吗', '呢', '什么', '为什么', '怎么', '如何', '难道'];
if (questionWords.some(word => result.includes(word)) && !result.endsWith('?')) {
result += '?';
} else if (!result.endsWith('。') && !result.endsWith('!') && !result.endsWith('?')) {
// 默认添加句号
result += '。';
}
return result;
}
// 监听来自popup的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
switch (request.action) {
case 'startRecording':
startRecording(request.language, request.autoPunctuation);
break;
case 'stopRecording':
stopRecording();
break;
case 'startSubtitle':
subtitleActive = true;
if (!subtitleElement) {
subtitleElement = createSubtitleElement();
}
subtitleElement.style.display = 'block';
break;
case 'stopSubtitle':
subtitleActive = false;
if (subtitleElement) {
subtitleElement.style.display = 'none';
}
break;
}
});
// 监听页面上的视频元素,为视频添加字幕支持
function setupVideoSubtitles() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
// 如果视频有音频轨道,可以尝试为其生成字幕
if (!video.dataset.sensevoiceProcessed) {
video.dataset.sensevoiceProcessed = 'true';
// 这里可以扩展为从视频提取音频并生成字幕
// 由于涉及音频提取和实时处理,这里只做框架展示
}
});
}
// 初始设置
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupVideoSubtitles);
} else {
setupVideoSubtitles();
}
// 监听动态加载的视频
const observer = new MutationObserver(setupVideoSubtitles);
observer.observe(document.body, { childList: true, subtree: true });
background.js - 后台服务脚本
// 后台服务,处理扩展的长期任务
chrome.runtime.onInstalled.addListener(() => {
console.log('SenseVoice扩展已安装');
});
// 监听扩展图标点击
chrome.action.onClicked.addListener((tab) => {
// 这里可以添加点击图标时的逻辑
// 比如打开配置页面或显示状态信息
});
// 存储用户设置
chrome.storage.onChanged.addListener((changes, namespace) => {
for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(`设置变更: ${key} 从 ${oldValue} 变为 ${newValue}`);
}
});
4.3 图标准备
你需要准备三个尺寸的图标(PNG格式):
- 16x16像素(用于工具栏)
- 48x48像素(用于扩展管理页面)
- 128x128像素(用于应用商店)
可以使用简单的设计工具创建,或者用SenseVoice的logo。如果没有设计资源,可以先使用纯色图标占位。
5. 安装和测试扩展
5.1 加载扩展程序
- 打开Chrome浏览器,在地址栏输入:
chrome://extensions/ - 开启右上角的“开发者模式”
- 点击“加载已解压的扩展程序”
- 选择你创建的
sensevoice-extension文件夹 - 扩展程序应该会出现在列表中
5.2 配置本地服务地址
确保SenseVoice WebUI服务正在运行(http://localhost:7860)。如果服务运行在其他地址或端口,需要修改 content.js 中的API地址。
5.3 功能测试
测试1:语音输入
- 打开任何一个有输入框的网页(如Google搜索、Gmail、Notion等)
- 点击输入框,让光标闪烁
- 点击扩展图标,选择语言,点击“开始语音输入”
- 对着麦克风说话,观察文字是否自动输入
测试2:实时字幕
- 打开一个视频网站(如YouTube)
- 点击扩展图标,点击“开启实时字幕”
- 播放视频,观察底部是否出现字幕
- 注意:这个功能需要视频有音频轨道,并且浏览器能捕获到系统音频(可能需要额外配置)
测试3:多语言识别
- 用不同语言说话,测试自动检测功能
- 手动选择特定语言,测试识别准确率
6. 高级功能扩展
基本的语音输入和字幕功能已经实现了,但我们可以让它更强大。这里分享几个进阶功能的实现思路:
6.1 快捷键支持
让用户可以通过快捷键(如Ctrl+Shift+S)快速开始/停止录音,而不需要点击扩展图标。
// 在content.js中添加
document.addEventListener('keydown', (event) => {
// Ctrl+Shift+S 开始/停止录音
if (event.ctrlKey && event.shiftKey && event.key === 'S') {
event.preventDefault();
if (isRecording) {
stopRecording();
} else {
const language = 'auto'; // 可以从存储中获取用户设置
startRecording(language, true);
}
}
});
6.2 语音命令控制
实现简单的语音命令,比如“清空输入框”、“换行”、“保存”等。
// 语音命令识别
function processVoiceCommand(text) {
const commands = {
'清空': () => {
const activeElement = document.activeElement;
if (activeElement.value !== undefined) {
activeElement.value = '';
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
}
},
'换行': () => {
document.execCommand('insertText', false, '\n');
},
'保存': () => {
// 触发保存操作,具体取决于页面
document.dispatchEvent(new KeyboardEvent('keydown', {
key: 's',
ctrlKey: true
}));
}
};
for (const [command, action] of Object.entries(commands)) {
if (text.includes(command)) {
action();
return text.replace(command, '').trim();
}
}
return text;
}
6.3 离线缓存支持
即使SenseVoice服务暂时不可用,也能提供基本的语音记录功能。
// 离线缓存实现
class AudioCache {
constructor() {
this.cache = new Map();
this.maxSize = 10; // 最多缓存10条录音
}
addRecording(key, audioBlob, text) {
this.cache.set(key, { audioBlob, text, timestamp: Date.now() });
// 清理旧缓存
if (this.cache.size > this.maxSize) {
const oldestKey = [...this.cache.entries()]
.reduce((oldest, current) =>
current[1].timestamp < oldest[1].timestamp ? current : oldest)[0];
this.cache.delete(oldestKey);
}
}
getRecording(key) {
return this.cache.get(key);
}
getAllRecordings() {
return [...this.cache.entries()].map(([key, value]) => ({
key,
...value
}));
}
}
6.4 自定义识别后处理
让用户可以添加自定义的文本处理规则,比如自动替换特定词汇、添加格式等。
// 用户自定义规则处理
const userRules = [
{ pattern: /github/gi, replacement: 'GitHub' },
{ pattern: /javascript/gi, replacement: 'JavaScript' },
{ pattern: /(\d+)元/g, replacement: '¥$1' }
];
function applyUserRules(text) {
let result = text;
userRules.forEach(rule => {
result = result.replace(rule.pattern, rule.replacement);
});
return result;
}
7. 实际应用场景
这个扩展不仅仅是一个技术演示,它在实际工作中有很多实用场景:
7.1 内容创作者
- 视频字幕生成:为自制视频快速添加字幕
- 播客转录:将音频内容转为文字稿
- 采访整理:录音采访自动转文字,提高整理效率
7.2 办公效率
- 会议记录:线上会议自动记录,会后直接出纪要
- 邮件撰写:口述邮件内容,自动生成草稿
- 文档创作:语音输入代替打字,提高写作速度
7.3 学习辅助
- 外语学习:实时生成视频字幕,辅助语言学习
- 课程笔记:听课同时自动生成文字笔记
- 资料整理:语音记录灵感,自动转为文字
7.4 无障碍支持
- 听力辅助:为听力障碍者提供实时字幕
- 语音控制:通过语音操作网页内容
- 多语言沟通:实时翻译和转写外语内容
8. 性能优化建议
如果你发现扩展运行不够流畅,可以尝试以下优化:
8.1 音频处理优化
// 使用更高效的音频格式
const audioConfig = {
mimeType: 'audio/webm;codecs=opus',
audioBitsPerSecond: 16000, // 降低比特率
sampleRate: 16000 // 降低采样率
};
// 分段发送音频,减少延迟
mediaRecorder.ondataavailable = async (event) => {
if (event.data.size > 0) {
// 立即发送这一小段音频
const result = await sendAudioChunk(event.data);
if (result.text) {
// 实时更新,而不是等录音结束
updateTextIncrementally(result.text);
}
}
};
8.2 网络请求优化
// 实现请求队列和重试机制
class RequestQueue {
constructor(maxConcurrent = 2) {
this.queue = [];
this.activeCount = 0;
this.maxConcurrent = maxConcurrent;
}
async add(requestFn) {
return new Promise((resolve, reject) => {
this.queue.push({ requestFn, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.activeCount >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.activeCount++;
const { requestFn, resolve, reject } = this.queue.shift();
try {
const result = await this.retryRequest(requestFn, 3);
resolve(result);
} catch (error) {
reject(error);
} finally {
this.activeCount--;
this.processQueue();
}
}
async retryRequest(requestFn, maxRetries) {
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
}
8.3 内存管理
// 定期清理资源
setInterval(() => {
// 清理过期的音频数据
const oneHourAgo = Date.now() - 60 * 60 * 1000;
for (const [key, data] of audioCache.entries()) {
if (data.timestamp < oneHourAgo) {
audioCache.delete(key);
}
}
// 清理DOM元素
const indicators = document.querySelectorAll('.temp-indicator');
indicators.forEach(indicator => {
if (Date.now() - indicator.dataset.created > 5000) {
indicator.remove();
}
});
}, 300000); // 每5分钟清理一次
9. 故障排除
9.1 常见问题及解决
问题1:扩展无法连接本地服务
可能原因:
- SenseVoice服务未启动
- 防火墙阻止了连接
- 服务运行在不同的端口
解决方法:
// 在popup.js中添加多端口尝试
async function tryConnect(port) {
try {
const response = await fetch(`http://localhost:${port}/health`);
return response.ok ? port : null;
} catch {
return null;
}
}
async function findServicePort() {
const ports = [7860, 7861, 7862, 8000, 8080];
for (const port of ports) {
const result = await tryConnect(port);
if (result) return result;
}
return null;
}
问题2:麦克风权限被拒绝
解决方法:
- 检查浏览器麦克风权限设置
- 确保没有其他应用占用麦克风
- 尝试在浏览器设置中重置权限
问题3:识别速度慢
解决方法:
- 降低音频质量设置
- 使用更短的录音分段
- 检查本地服务性能
问题4:字幕显示位置不对
解决方法:
// 动态调整字幕位置
function adjustSubtitlePosition() {
if (!subtitleElement) return;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
// 避免被页面元素遮挡
subtitleElement.style.bottom = '100px';
subtitleElement.style.left = '50%';
subtitleElement.style.transform = 'translateX(-50%)';
subtitleElement.style.maxWidth = `${viewportWidth * 0.8}px`;
}
10. 总结
通过这个Chrome扩展,我们将本地的SenseVoice-small语音识别服务变成了一个随时可用的浏览器工具。这个方案有几个显著优势:
隐私安全:所有语音处理都在本地完成,敏感信息不会上传到云端。
多语言支持:自动检测50多种语言,满足国际化需求。
低资源占用:基于ONNX量化模型,即使在普通设备上也能流畅运行。
高度可定制:开源代码允许你根据具体需求进行调整和扩展。
离线可用:一旦部署完成,即使没有网络也能使用核心功能。
这个扩展只是一个起点,你可以在此基础上添加更多功能,比如:
- 语音翻译功能
- 自定义命令词
- 批量处理历史录音
- 与其他工具集成(如Notion、Google Docs等)
- 团队协作功能
最重要的是,这个方案展示了如何将先进的AI能力以最便捷的方式集成到日常工具中。技术不应该只是实验室里的演示,而应该成为我们工作和生活中实实在在的生产力工具。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)