Chrome 插件开发到发布完整指南:从零开始打造 TTS 朗读助手

Chrome 插件开发到发布完整指南:从零开始打造 TTS 朗读助手

本文将从零开始,完整介绍如何开发一个 Chrome 浏览器插件,并发布到 GitHub 供用户下载使用。以 TTS 朗读助手为例,涵盖开发、调试、打包、发布的完整流程。

📋 目录

🎯 项目概述

项目简介

开发一个基于阿里云 TTS 服务的浏览器朗读助手,支持选中文本朗读、播放控制、右键菜单等功能。

功能特性

  • 🎯 智能朗读:选中网页文本,一键朗读
  • 🎨 多种音色:支持多种语音音色选择
  • 🎵 播放控制:支持播放、暂停、停止功能
  • 🎪 视觉反馈:播放时图标动态显示
  • 🖱️ 右键菜单:右键选中文本即可朗读
  • 🔒 安全可靠:基于阿里云 TTS 服务

技术栈

  • 前端:HTML + CSS + JavaScript
  • TTS 服务:阿里云通义千问 TTS
  • 浏览器 API:Chrome Extension API
  • 版本控制:Git + GitHub

🛠️ 开发环境准备

必需工具

  1. Chrome 浏览器:88.0 或更高版本
  2. 代码编辑器:VS Code、Sublime Text 等
  3. Git:版本控制
  4. GitHub 账号:代码托管和发布

目录结构

python 复制代码
xuri-tts-assistant/
├── extension/                    # 插件源码目录
│   ├── manifest.json            # 插件配置文件
│   ├── background.js            # 后台脚本
│   ├── content.js               # 内容脚本
│   ├── popup.html               # 弹窗页面
│   ├── popup.js                 # 弹窗脚本
│   ├── popup.css                # 弹窗样式
│   ├── icon.png                 # 插件图标
│   └── icon-playing.png         # 播放状态图标
├── README.md                    # 项目说明
├── LICENSE                      # 开源许可证
└── xuri-tts-assistant.zip       # 打包文件

🏗️ 插件架构设计

Chrome 插件架构

Chrome 插件由以下几个核心部分组成:

  1. Manifest.json:插件配置文件,定义权限、脚本等
  2. Background Script:后台脚本,处理全局逻辑
  3. Content Script:内容脚本,与网页交互
  4. Popup:弹窗界面,用户操作入口
  5. Icons:插件图标,视觉标识

数据流向

css 复制代码
用户操作 → Popup → Background Script → TTS API → Content Script → 音频播放

💻 核心功能开发

1. 创建 Manifest.json

json 复制代码
{
  "manifest_version": 3,
  "name": "Xuri TTS Assistant",
  "version": "1.0",
  "description": "浏览器朗读助手,支持智能文本朗读",
  "permissions": [
    "storage",
    "contextMenus",
    "notifications",
    "scripting",
    "tabs",
    "activeTab"
  ],
  "host_permissions": ["<all_urls>"],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

关键配置说明

  • manifest_version: 3:使用最新的 Manifest V3
  • permissions:声明需要的权限
  • host_permissions:允许访问的网站
  • content_scripts:注入到网页的脚本

2. 开发 Background Script

javascript 复制代码
// background.js
let isPlaying = false;

// 创建右键菜单
chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: "tts-read-selection",
    title: "朗读选中文本",
    contexts: ["selection"],
  });
});

// 监听右键菜单点击
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId === "tts-read-selection" && info.selectionText) {
    const text = info.selectionText.trim();
    if (text) {
      try {
        const result = await qwenTTS(text, "Cherry", false);
        if (result.output && result.output.audio) {
          playAudio(result.output.audio.url);
        }
      } catch (error) {
        showNotification("TTS 服务调用失败: " + error.message);
      }
    }
  }
});

// TTS API 调用
async function qwenTTS(text, voice = "Cherry", stream = false) {
  const apiUrl =
    "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation";
  const apiKey = "your-api-key"; // 替换为你的 API Key

  const response = await fetch(apiUrl, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${apiKey}`,
    },
    body: JSON.stringify({
      model: "qwen-tts",
      input: { text, voice, stream },
    }),
  });

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  return await response.json();
}

// 播放音频
function playAudio(url) {
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    if (tabs.length > 0) {
      chrome.tabs.sendMessage(tabs[0].id, { action: "play-audio", url });
    }
  });
}

// 更新图标状态
function updateIcon(playing) {
  const iconPath = playing ? "icon-playing.png" : "icon.png";
  chrome.action.setIcon({
    path: { 16: iconPath, 32: iconPath, 48: iconPath, 128: iconPath },
  });
  chrome.action.setTitle({
    title: playing ? "朗读助手 - 正在播放" : "朗读助手",
  });
}

3. 开发 Content Script

javascript 复制代码
// content.js
let audio = null;

// 监听来自 background script 的消息
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.action === "play-audio" && msg.url) {
    playAudio(msg.url);
  } else if (msg.action === "pause-audio") {
    pauseAudio();
  } else if (msg.action === "stop-audio") {
    stopAudio();
  }
});

// 播放音频
function playAudio(url) {
  if (audio) {
    audio.pause();
    audio = null;
  }

  audio = new Audio(url);

  // 音频事件监听
  audio.onloadstart = () => {
    chrome.runtime.sendMessage({ action: "audio-started" });
  };

  audio.onplay = () => {
    chrome.runtime.sendMessage({ action: "audio-started" });
  };

  audio.onended = () => {
    chrome.runtime.sendMessage({ action: "audio-stopped" });
  };

  audio.onerror = (e) => {
    chrome.runtime.sendMessage({ action: "audio-stopped" });
    console.error("音频播放失败:", e);
  };

  audio.play().catch((e) => {
    chrome.runtime.sendMessage({ action: "audio-stopped" });
    console.error("音频播放失败:", e);
  });
}

// 暂停音频
function pauseAudio() {
  if (audio && !audio.paused) {
    audio.pause();
  }
}

// 停止音频
function stopAudio() {
  if (audio) {
    audio.pause();
    audio.currentTime = 0;
    chrome.runtime.sendMessage({ action: "audio-stopped" });
  }
}
html 复制代码
<!-- popup.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="popup.css" />
  </head>
  <body>
    <div class="container">
      <h2>朗读助手</h2>

      <div class="voice-selector">
        <label for="voice">选择音色:</label>
        <select id="voice">
          <option value="Cherry">Cherry (女声)</option>
          <option value="Allen">Allen (男声)</option>
          <option value="Zhiyan">Zhiyan (知言)</option>
        </select>
      </div>

      <div class="controls">
        <button id="startBtn" class="btn primary">开始朗读</button>
        <button id="pauseBtn" class="btn secondary" disabled>暂停</button>
        <button id="stopBtn" class="btn secondary" disabled>停止</button>
      </div>

      <div class="status" id="status"></div>
    </div>

    <script src="popup.js"></script>
  </body>
</html>
css 复制代码
/* popup.css */
body {
  width: 300px;
  padding: 20px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

.container {
  text-align: center;
}

h2 {
  color: #333;
  margin-bottom: 20px;
}

.voice-selector {
  margin-bottom: 20px;
}

.voice-selector label {
  display: block;
  margin-bottom: 8px;
  color: #666;
}

.voice-selector select {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.controls {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.btn {
  flex: 1;
  padding: 10px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: background-color 0.2s;
}

.btn.primary {
  background-color: #4caf50;
  color: white;
}

.btn.primary:hover {
  background-color: #45a049;
}

.btn.secondary {
  background-color: #f0f0f0;
  color: #333;
}

.btn.secondary:hover {
  background-color: #e0e0e0;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.status {
  color: #666;
  font-size: 12px;
  min-height: 16px;
}
javascript 复制代码
// popup.js
document.addEventListener("DOMContentLoaded", () => {
  const startBtn = document.getElementById("startBtn");
  const pauseBtn = document.getElementById("pauseBtn");
  const stopBtn = document.getElementById("stopBtn");
  const voiceSelect = document.getElementById("voice");
  const status = document.getElementById("status");

  // 开始朗读
  startBtn.addEventListener("click", () => {
    const voice = voiceSelect.value;
    chrome.runtime.sendMessage({ action: "start", voice });
    updateStatus("正在获取选中文本...");
  });

  // 暂停
  pauseBtn.addEventListener("click", () => {
    chrome.runtime.sendMessage({ action: "pause" });
    updateStatus("已暂停");
  });

  // 停止
  stopBtn.addEventListener("click", () => {
    chrome.runtime.sendMessage({ action: "stop" });
    updateStatus("已停止");
  });

  // 更新状态
  function updateStatus(message) {
    status.textContent = message;
  }

  // 监听播放状态
  chrome.runtime.onMessage.addListener((msg) => {
    if (msg.action === "audio-started") {
      startBtn.disabled = true;
      pauseBtn.disabled = false;
      stopBtn.disabled = false;
      updateStatus("正在播放...");
    } else if (msg.action === "audio-stopped") {
      startBtn.disabled = false;
      pauseBtn.disabled = true;
      stopBtn.disabled = true;
      updateStatus("播放完成");
    }
  });
});

🐛 调试与测试

1. 本地调试

  1. 加载插件

    • 打开 Chrome,访问 chrome://extensions/
    • 开启"开发者模式"
    • 点击"加载已解压的扩展程序"
    • 选择 extension 文件夹
  2. 调试 Background Script

    • 在扩展页面点击"检查视图"
    • 使用 Console 和 Sources 面板调试
  3. 调试 Content Script

    • 在目标网页按 F12 打开开发者工具
    • 在 Console 中查看日志
  4. 调试 Popup

    • 右键点击插件图标
    • 选择"检查弹出内容"

2. 常见调试技巧

javascript 复制代码
// 添加调试日志
console.log("Debug:", { text, voice, result });

// 错误处理
try {
  const result = await qwenTTS(text, voice);
  console.log("TTS Result:", result);
} catch (error) {
  console.error("TTS Error:", error);
  showNotification("TTS 服务调用失败: " + error.message);
}

// 检查权限
chrome.permissions.contains(
  {
    permissions: ["activeTab"],
    origins: ["<all_urls>"],
  },
  (result) => {
    console.log("Permissions check:", result);
  }
);

3. 测试清单

  • 插件能正常加载
  • 右键菜单功能正常
  • 弹窗界面显示正确
  • TTS API 调用成功
  • 音频播放功能正常
  • 播放状态图标更新
  • 错误处理机制有效
  • 在不同网站测试兼容性

📦 打包与发布

1. 准备发布文件

bash 复制代码
# 创建打包文件
zip -r xuri-tts-assistant.zip extension/

# 检查文件大小
ls -la xuri-tts-assistant.zip

2. 创建发布文档

README.md
markdown 复制代码
# Xuri TTS Assistant - 浏览器朗读助手

## 功能特性

- 🎯 智能朗读:选中网页文本,一键朗读
- 🎨 多种音色:支持多种语音音色选择
- 🎵 播放控制:支持播放、暂停、停止功能
- 🎪 视觉反馈:播放时图标动态显示
- 🖱️ 右键菜单:右键选中文本即可朗读

## 安装方法

1. 下载 `xuri-tts-assistant.zip`
2. 解压文件
3. 打开 Chrome,进入 `chrome://extensions/`
4. 开启"开发者模式"
5. 点击"加载已解压的扩展程序"
6. 选择解压后的 `extension` 文件夹

## 使用方法

1. 在网页上选中要朗读的文本
2. 点击插件图标,选择音色后点击"开始朗读"
3. 或右键选中文本,选择"朗读选中文本"
RELEASE_NOTES.md
markdown 复制代码
# Xuri TTS Assistant v1.0 发布说明

## 🎉 首个正式版本发布

### ✨ 主要功能

- 智能文本朗读
- 多种音色支持
- 播放控制功能
- 右键菜单集成
- 播放状态显示

### 📦 安装方法

1. 下载 `xuri-tts-assistant.zip`
2. 按 README.md 中的步骤安装

### 🔧 技术特性

- TTS 引擎:阿里云通义千问 TTS
- 浏览器支持:Chrome 88+
- 权限要求:最小化权限设计

3. GitHub 发布流程

bash 复制代码
# 1. 提交代码
git add .
git commit -m "feat: 完成插件开发并准备发布 v1.0"
git push origin main

# 2. 创建版本标签
git tag -a v1.0 -m "Release v1.0: 首个正式版本"
git push origin v1.0

# 3. 在 GitHub 创建 Release
# - 访问仓库的 Releases 页面
# - 点击 "Create a new release"
# - 选择标签 v1.0
# - 填写发布说明
# - 上传 xuri-tts-assistant.zip
# - 点击 "Publish release"

4. 发布检查清单

  • 所有功能测试通过
  • 代码注释完整
  • 错误处理完善
  • 文档齐全(README、LICENSE、隐私政策)
  • 打包文件创建
  • GitHub Release 发布
  • 安装说明清晰

🔧 常见问题解决

1. 权限错误

yaml 复制代码
Unchecked runtime.lastError: Cannot access contents of url

解决方案

  • manifest.json 中添加 host_permissions
  • 确保权限声明正确

2. 连接错误

arduino 复制代码
Uncaught (in promise) Error: Could not establish connection

解决方案

  • 检查 content script 是否正确注入
  • 添加错误处理和重试机制
  • 排除特殊页面(chrome:// 等)

3. API 调用失败

复制代码
TTS 服务调用失败

解决方案

  • 检查 API Key 是否正确
  • 验证网络连接
  • 确认 API 配额充足

4. 音频播放问题

复制代码
音频播放失败

解决方案

  • 检查音频 URL 是否有效
  • 确认浏览器支持音频格式
  • 添加音频加载错误处理

📈 性能优化

1. 代码优化

javascript 复制代码
// 使用防抖处理频繁操作
function debounce(func, wait) {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
}

// 缓存 API 结果
const audioCache = new Map();
function getCachedAudio(text, voice) {
  const key = `${text}-${voice}`;
  if (audioCache.has(key)) {
    return audioCache.get(key);
  }
  // 调用 API 并缓存结果
}

2. 资源优化

  • 压缩图片文件
  • 合并 CSS/JS 文件
  • 使用 CDN 加速
  • 启用 Gzip 压缩

3. 用户体验优化

  • 添加加载动画
  • 优化错误提示
  • 支持快捷键操作
  • 记住用户偏好设置

🚀 扩展功能

1. 功能扩展

  • 支持更多 TTS 服务
  • 添加语音设置选项
  • 支持批量文本朗读
  • 添加朗读历史记录
  • 支持更多浏览器

2. 技术升级

  • 使用 WebAssembly 优化性能
  • 添加离线 TTS 支持
  • 实现语音识别功能
  • 支持多语言界面

3. 商业化方向

  • 发布到 Chrome Web Store
  • 提供付费高级功能
  • 企业版本定制
  • API 服务商业化

📚 学习资源

官方文档

开发工具

社区资源

🎯 总结与展望

开发总结

通过本教程,我们完成了:

  1. 完整的开发流程:从需求分析到功能实现
  2. 规范的代码结构:模块化设计,易于维护
  3. 完善的错误处理:提升用户体验
  4. 详细的文档说明:便于用户使用和开发者贡献

技术收获

  • Chrome Extension API 的使用
  • Manifest V3 的新特性
  • 前后端分离的架构设计
  • 异步编程和错误处理
  • 用户体验优化技巧

未来展望

Chrome 插件开发是一个充满可能性的领域:

  1. 技术发展:随着 Web 技术的进步,插件功能将更加强大
  2. 应用场景:从工具类到娱乐类,应用场景不断扩展
  3. 商业模式:从免费开源到商业化运营,变现方式多样
  4. 生态建设:开发者社区活跃,资源共享丰富

建议

  1. 持续学习:关注 Chrome 插件技术的最新发展
  2. 实践项目:多开发不同类型的插件项目
  3. 开源贡献:参与开源项目,提升技术水平
  4. 社区交流:加入开发者社区,分享经验和资源

感谢阅读! 🎉

如果这个教程对你有帮助,请给个 ⭐ Star 支持一下!


本文首发于掘金,转载请注明出处。

相关推荐
孤水寒月1 小时前
给自己网站增加一个免费的AI助手,纯HTML
前端·人工智能·html
CoderLiu1 小时前
用这个MCP,只给大模型一个figma链接就能直接导出图片,还能自动压缩上传?
前端·llm·mcp
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之实现电子签名效果
开发语言·前端·华为·harmonyos·鸿蒙·鸿蒙系统
海的诗篇_2 小时前
前端开发面试题总结-原生小程序部分
前端·javascript·面试·小程序·vue·html
uncleTom6662 小时前
前端地图可视化的新宠儿:Cesium 地图封装实践
前端
lemonzoey2 小时前
无缝集成 gemini-cli 的 vscode 插件:shenma
前端·人工智能
老家的回忆2 小时前
jsPDF和html2canvas生成pdf,组件用的elementplus,亲测30多页,20s实现
前端·vue.js·pdf·html2canvas·jspdf
半点寒12W2 小时前
uniapp全局状态管理实现方案
前端
Vertira2 小时前
pdf 合并 python实现(已解决)
前端·python·pdf
PeterJXL3 小时前
Chrome 下载文件时总是提示“已阻止不安全的下载”的解决方案
前端·chrome·安全