前言
接近8.9年老前端了,34岁,双非普本,坐标广州,25年底被裁员,然后这三个月内也有去投简历,也有面试,有一些推进到二面然后就没有下文,不禁感叹现在的大环境实在不怎么样,而且前端在AI的冲击下也是最受影响的,除了音视频,图形化方面还能蹦跶一下,AI已经能完成80-90%的前端工作,在学历以及就业背景都不是特别强的情况下,一般的前端哪怕你技术还不错,你也很缺竞争力;在失业这三个月经历了持续学习、迷茫到看到曙光,决定要转型自学做AI agent;
大纲
- 前一天心路历程
- 前一天的时间分配(不限于学习,也会有运动)
- 前一天的知识总结(前期或许较少)
心路历程
这两天在做项目总结整理文档的时候上外网看了一些生肉视频资源就突然心血来潮想搞一个chrome的实时语音识别输出中文字幕的插件,然后就有了这个项目,明天得回归到学习心得AI项目实践了
时间分配
这两天都以休息复盘整理文档为主
知识总结
TransLens - Chrome 插件开发与实时语音翻译实战教程
你好!欢迎来到 Chrome 插件开发的奇妙世界。这篇文档是专门为你准备的 TransLens 项目深度拆解指南。即使你的基础薄弱,也不用担心。我们将把这个项目拆解成生活中的例子,带你一步步理解它的核心原理 and 开发流程。
看完这篇文档,你不仅能掌握 Chrome 插件 (Manifest V3) 的核心架构 ,还能学会如何截获网页音频、如何使用 WebSockets,以及如何优雅地在别人网页里注入不冲突的 UI。
目录
- [0. 项目效果展示](#0. 项目效果展示 "#0-%E9%A1%B9%E7%9B%AE%E6%95%88%E6%9E%9C%E5%B1%95%E7%A4%BA")
- [1. 整体结构与设计理念:我们为什么这么做?](#1. 整体结构与设计理念:我们为什么这么做? "#1-%E6%95%B4%E4%BD%93%E7%BB%93%E6%9E%84%E4%B8%8E%E8%AE%BE%E8%AE%A1%E7%90%86%E5%BF%B5%E6%88%91%E4%BB%AC%E4%B8%BA%E4%BB%80%E4%B9%88%E8%BF%99%E4%B9%88%E5%81%9A")
- [2. 业务流程与开发详解(手把手教学)](#2. 业务流程与开发详解(手把手教学) "#2-%E4%B8%9A%E5%8A%A1%E6%B5%81%E7%A8%8B%E4%B8%8E%E5%BC%80%E5%8F%91%E8%AF%A6%E8%A7%A3%E6%89%8B%E6%8A%8A%E6%89%8B%E6%95%99%E5%AD%A6")
- 第一棒:用户点击"开始"
- 第二棒:获取录音权限并唤醒暗房
- [第三棒:暗房疯狂截取音频发给 AI](#第三棒:暗房疯狂截取音频发给 AI "#%E7%AC%AC%E4%B8%89%E6%A3%92%E6%9A%97%E6%88%BF%E7%96%AF%E7%8B%82%E6%88%AA%E5%8F%96%E9%9F%B3%E9%A2%91%E5%8F%91%E7%BB%99-ai-offscreen---websocket---python-proxy")
- [第四棒:AI 吐出字幕,前线施工队渲染](#第四棒:AI 吐出字幕,前线施工队渲染 "#%E7%AC%AC%E5%9B%9B%E6%A3%92ai-%E5%90%90%E5%87%BA%E5%AD%97%E5%B9%95%E5%89%8D%E7%BA%BF%E6%96%BD%E5%B7%A5%E9%98%9F%E6%B8%B2%E6%9F%93-offscreen---background---content")
- [3. 关键知识点大白话解析](#3. 关键知识点大白话解析 "#3-%E5%85%B3%E9%94%AE%E7%9F%A5%E8%AF%86%E7%82%B9%E5%A4%A7%E7%99%BD%E8%AF%9D%E8%A7%A3%E6%9E%90")
- [Service Worker](#Service Worker "#1-service-worker-%E5%AF%B9%E5%BA%94-backgroundindexts")
- [Offscreen Document](#Offscreen Document "#2-offscreen-document-%E5%AF%B9%E5%BA%94-offscreenhtml")
- [Shadow DOM](#Shadow DOM "#3-shadow-dom-%E5%AF%B9%E5%BA%94-content-%E6%B3%A8%E5%85%A5%E9%80%BB%E8%BE%91")
- WebSocket
- [4. 攻克的难关](#4. 攻克的难关 "#4-%E6%94%BB%E5%85%8B%E7%9A%84%E9%9A%BE%E5%85%B3")
项目效果展示


1. 整体结构与设计理念:我们为什么这么做?
TransLens 的目标是:实时截获当前网页正在播放的声音(比如看无字幕外语视频),将其发送给 AI 识别,并把翻译后的字幕悬浮显示在网页上。
为了实现这个目标,我们的项目采用了 Chrome Extension Manifest V3 (MV3) + React + Vite + Python 代理服务 的架构。整个项目由以下几个"部门"协同工作:
项目结构分布:
src/popup(控制面板):你点击浏览器右上角插件图标弹出的界面。用来填写 API 密钥、选择引擎、点击"开始捕获"。src/background(调度中心 / Service Worker) :插件的大脑。运行在浏览器后台,负责协调各个部门,但它没有视觉界面(不能访问 DOM),也不能录音。src/offscreen(幕后暗房) :这是 MV3 时代最重要的设计! 因为后台大脑(Background)被剥夺了录音权限,且随时可能休眠。我们不得不创建一个用户看不见的隐形网页(Offscreen Document),专门在这个网页里开启录音(Web Audio API)并维持与服务器的长连接(WebSocket)。src/content(前线施工队 / Content Script):这段代码会被"强行注入"到用户当前正在看的网页中。它的任务就是在网页里画一个漂亮的字幕框。proxy_server.py(本地中转站):因为部分云端 AI 服务(如火山引擎)要求复杂的鉴权或特殊的 HTTP 请求头,而浏览器的 WebSocket 无法随意修改这些头,所以我们用 Python 跑了一个本地中转站。
为什么这么设计?(设计理念)
- 为什么不直接在 Background 里录音? Chrome 官方为了省电和安全,在最新的 MV3 标准中使用了 Service Worker 替代了以前的后台页。Service Worker 没有网页环境(DOM),调不了音频接口。所以我们用 Offscreen 充当"干脏活的替身"。
- 为什么字幕要用 Shadow DOM? 如果直接在网页里加个
<div>写字幕,网页原本的 CSS 样式(比如字体变大、颜色变红)可能会污染我们的字幕。Shadow DOM 就像一个隔离的"玻璃罩",外面的样式进不来,里面的样式出不去,保证字幕永远美观。
2. 业务流程与开发详解(手把手教学)
整个流程可以分为四个接力棒。我们将详细讲解每一个接力棒是如何交接的。
第一棒:用户点击"开始" (Popup -> Background)
业务流程:用户在弹窗(Popup)输入完密钥,点击"开始捕获"。Popup 获取当前正在看的标签页 ID,并告诉"调度中心"(Background):"喂,开始干活了!"
核心代码 (src/popup/index.tsx):
typescript
// 1. 获取当前活跃的标签页
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// 2. 发送消息给后台调度中心 (Background)
// 这里用到了 Chrome 的消息通信 API
chrome.runtime.sendMessage({
type: 'START_CAPTURE',
tabId: tab.id
}, (response) => {
if (response.success) {
console.log('启动成功!');
}
});
第二棒:获取录音权限并唤醒暗房 (Background -> Offscreen)
业务流程:Background 收到指令后,首先去向 Chrome 申请捕获该标签页声音的"许可证"(Stream ID)。拿到许可证后,它唤醒隐藏的"暗房"(Offscreen),把许可证交给它,让它去录音。
核心代码 (src/background/index.ts):
typescript
async function handleStartCapture(tabId: number) {
// 1. 唤醒暗房 (创建 Offscreen Document)
// 这是 MV3 的关键 API,告诉浏览器我们需要一个不可见的网页来录音 (USER_MEDIA)
if (!(await chrome.offscreen.hasDocument())) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: [chrome.offscreen.Reason.USER_MEDIA], // 理由:使用媒体设备
justification: 'Recording tab audio for translation'
});
}
// 2. 申请标签页录音许可证 (MediaStreamId)
const streamId = await new Promise<string>((resolve) => {
chrome.tabCapture.getMediaStreamId({ targetTabId: tabId }, (id) => resolve(id));
});
// 3. 把许可证交给暗房 (发送消息)
chrome.runtime.sendMessage({
type: 'START_AUDIO_CAPTURE',
streamId: streamId,
// ... 附带 API keys 等配置
});
}
第三棒:暗房疯狂截取音频发给 AI (Offscreen -> WebSocket -> Python Proxy)
业务流程:暗房收到许可证,立刻开启录音。它把连续的声音切成一小块一小块的"数据包"(PCM数据),通过 WebSocket 一刻不停地发给 Python 代理服务器,代理服务器再转给 AI(豆包或阿里云)。
核心代码 (src/offscreen/index.ts):
typescript
async function startCapture(streamId: string) {
// 1. 凭许可证 (streamId) 真正拿到音频流
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'tab', // 指定录制标签页,而不是麦克风
chromeMediaSourceId: streamId
}
} as any
});
// 2. 建立音频加工厂 (AudioContext)
const audioContext = new AudioContext({ sampleRate: 16000 }); // AI 需要 16kHz
// 引入我们自己写的处理器,用来切碎音频
await audioContext.audioWorklet.addModule('audio-processor.js');
const source = audioContext.createMediaStreamSource(stream);
const processor = new AudioWorkletNode(audioContext, 'pcm-processor');
// 3. 连接 WebSocket
const ws = new WebSocket(`ws://127.0.0.1:8765/ws`);
// 4. 每次音频被切碎(加工厂产出),就发给服务器
processor.port.onmessage = (event) => {
const audioBuffer = event.data;
// ... 进行 WAV 封装或 Base64 编码 ...
ws.send(packet.buffer);
};
// 将音频流输入到加工厂
source.connect(processor);
}
第四棒:AI 吐出字幕,前线施工队渲染 (Offscreen -> Background -> Content)
业务流程:WebSocket 收到 AI 返回的 JSON 字幕。Offscreen 把字幕发给 Background,Background 就像个邮局,精准地把字幕派发给当初那个被录音的标签页(Content Script)。Content Script 收到后,在屏幕上画出来。
核心代码 (src/content/index.tsx):
tsx
// 1. 创建字幕容器并注入到别人网页的 body 中
let container = document.createElement('div');
document.body.appendChild(container);
// 2. 核心:附加 Shadow DOM(防止样式冲突)
const shadowRoot = container.attachShadow({ mode: 'open' });
const shadowContainer = document.createElement('div');
shadowRoot.appendChild(shadowContainer);
// 3. 使用 React 监听消息并渲染 UI
const SubtitleOverlay = () => {
const [sentences, setSentences] = useState([]);
useEffect(() => {
// 监听来自 Background 的字幕更新消息
chrome.runtime.onMessage.addListener((request) => {
if (request.type === 'SUBTITLE_UPDATE') {
// 更新 React 状态,重新渲染字幕
setSentences(prev => updateSentences(prev, request.text, request.isFinal));
}
});
}, []);
return (
<div className="subtitle-box">
{sentences.map(s => <div key={s.id}>{s.text}</div>)}
</div>
);
};
// 4. 把 React 组件渲染进 Shadow DOM 里
createRoot(shadowContainer).render(<SubtitleOverlay />);
3. 关键知识点大白话解析
为了让你知其然更知其所以然,我们来消化一下这几个硬核概念:
1. Service Worker (对应 background/index.ts)
- 官方描述:一种独立于网页运行的事件驱动脚本,生命周期短暂,无法访问 DOM。
- 生活类比 :公司的"外包前台"。她不属于任何一个具体的部门(页面),有人打电话(事件)她就接,把任务分发下去。但她没有权力动公司里的设备(没有 DOM,不能录音),而且如果几分钟没电话,她就会打瞌睡(休眠)。这解释了为什么我们要把重度工作移交给 Offscreen。
2. Offscreen Document (对应 offscreen.html)
- 官方描述:允许扩展在后台使用 DOM API 创建不可见文档的机制。
- 生活类比 :公司的"地下暗房"。前台(Service Worker)搞不定的录音、长时间打电话(WebSocket),她就写个纸条(Message)塞给地下暗房。暗房有完整的设备,只要任务没结束,暗房就一直工作。
3. Shadow DOM (对应 content 注入逻辑)
- 官方描述:Web components 技术的一部分,允许将隐藏的 DOM 树附加到常规的 DOM 树中,实现封装。
- 生活类比 :"生化隔离箱" 。我们要在别人的网页(比如 YouTube)上写字幕,万一 YouTube 设置了
div { background: red; },我们的字幕也会变红。把字幕放进 Shadow DOM,就像放进了一个无菌透明箱,外面网页的 CSS 毒气渗透不进来,保证字幕原汁原味。
4. WebSocket (对应 proxy_server.py 和 ws = new WebSocket())
- 官方描述:在单个 TCP 连接上进行全双工通信的协议。
- 生活类比 :"打电话 vs 发短信"。普通的 HTTP 请求像发短信(发一次,回一次,挂断)。我们要实时语音翻译,一秒钟要传好几次声音,如果用 HTTP 就相当于每秒钟拨号挂断几十次。WebSocket 则是拨通了电话就不挂断(长连接),两边可以随便说话,延迟极低。
4. 攻克的难关
- MV3 的后台限制陷阱 :当你发现 Service Worker 没法调用音频 API 时,你没有硬碰硬,而是优雅地利用了最新的
chrome.offscreen架构,实现了隐式录音。 - 音频流的高级处理 :你跨越了普通的增删改查,深入了 Web Audio API,学会了用
AudioWorkletNode像处理字节流一样处理声波(PCM 格式),这是多媒体前端的高阶技能。 - UI 隔离与污染防御 :你熟练运用了
Shadow DOM保护自己的组件。这说明你不仅能把功能做出来,还能保证它在极其复杂的外部环境(任意网页)中健壮运行。 - 跨端网络通信:遇到浏览器 WebSocket 头限制时,你引入了 Python 代理服务,打通了 前端 -> 代理 -> 云端大模型 的全链路。
目标
成为AI agent工程师并且就业
帮助
需要大家的关注跟点赞,你们的关注点赞就是对我最大的鼓励,或许以后待以后我技术成熟时,你们中间的大佬还可以捞一捞我,感谢