Skip to content

增加语音功能

1. 数据库扩展(D1)

在 sentences 表中增加音频相关字段:

ALTER TABLE sentences ADD COLUMN audio\_path TEXT;          \-- R2对象键,如 "sentences/user123/abc.mp3"
ALTER TABLE sentences ADD COLUMN duration INTEGER;         \-- 音频时长(秒),用于UI显示
ALTER TABLE sentences ADD COLUMN waveform\_data TEXT;       \-- 可选,预生成波形JSON(若使用wavesurfer可跳过)

若您未来支持用户录制上传,还需一张 audio_uploads 日志表(非必须),便于追踪。


2. R2 存储配置

  • 创建存储桶:在 Cloudflare Dashboard 创建 R2 存储桶,例如 sentence-audio。
  • 访问权限:
    • 建议不公开读取,而是通过 Worker 生成临时签名 URL,这样可防盗链、控制过期时间,同时便于统计流量。
      • 若追求极致简单,也可设为公开读取,但需承担被盗链的风险(流量可能超出免费额度)。
  • 存储类别:务必选择 Standard,因为免费出口流量仅适用于标准存储(每月 1GB 出站流量)。
  • 文件命名:推荐按用户隔离,如 audio/{userId}/{sentenceId}_{timestamp}.mp3,避免冲突。

3. 后端 API 扩展(Worker)

3.1 上传音频接口

POST /api/sentences/:id/audio Content-Type: multipart/form-data

  • 身份验证:从请求头获取 session,验证用户是否拥有该句子。
  • 接收文件(限制大小,如 5MB),使用 audio/mpeg 验证。
  • 生成随机文件名,使用 await env.R2_BUCKET.put(key, file) 上传。
  • 获取文件元数据(如 httpMetadata.contentType),并获取时长(可使用 fluent-ffmpeg 或 mediainfo,但 Worker 环境不便,建议前端上传时同时传递时长)。
  • 更新 sentences 表中的 audio_path 和 duration。
  • 返回 { audioUrl: signedUrl }。

3.2 获取音频签名 URL(或直接播放)

若使用签名 URL,提供:

GET /api/sentences/:id/audio?expires=...

Worker 验证身份,生成 R2 签名 URL(有效期 1 小时),返回给前端。

更推荐的方式:直接在 Worker 中反向代理音频流,由 Worker 校验身份后,从 R2 读取并返回。这样完全隐藏 R2 地址,且可灵活设置缓存策略。

此方式不会产生额外费用(Worker 请求数在免费额度内),且方便添加 Referer 校验等防盗链。


4. 前端音频播放与交互

4.1 播放控制

在句子卡片或详情页,添加播放按钮

4.2 A-B 复读功能

  • 用户可通过设置起点 A 和终点 B(例如通过拖动进度条或点击按钮标记当前时间)。
  • 利用 ontimeupdate 监听 currentTime,当 currentTime >= B 时跳回 A。

可提供“设置A点”、“设置B点”按钮,以及开启/关闭复读的开关。

4.3 波形可视化(集成 wavesurfer.js)

  • 安装 wavesurfer.js:npm install wavesurfer.js
  • 在组件中创建 Wavesurfer 实例,挂载到容器。

注意:R2 默认支持 Range 请求,wavesurfer 通过 MediaElement 后端可直接使用音频 URL,无需额外配置,且能边下边播。


5. 成本与免费额度优化

组件 免费额度 本方案消耗
R2 存储 10 GB 存储,1 GB/月下载流量(Standard) 音频文件大小需控制(建议≤2MB/条),总存储量在10GB内
R2 操作 每月 1000 万次 A/B 类操作,100 万次 C 类操作 上传/下载操作极少,远低于限制
Worker 请求 10 万次/天 播放请求计入,正常使用可覆盖

优化策略:

  • 音频压缩:使用 MP3 128kbps 或更低,单条时长<30秒,文件体积<1MB。
  • 缓存:在 Worker 响应中增加 Cache-Control: public, max-age=3600,减少重复从 R2 读取。
  • 上传前压缩:前端录制时直接使用较低比特率,避免上传大文件。
  • 只存储用户上传/录制的音频,若使用 TTS,可考虑每次实时生成但不存储(或缓存一段时间),权衡存储成本。

6. 防盗链与安全

  • Worker 代理模式:所有音频请求必须经过 Worker,Worker 校验用户身份(会话),确保用户只能访问自己的音频。
  • 限制 Referer:可选检查 Referer 是否来自您的域名,但容易被伪造,更可靠的是使用签名 URL。
  • 生成短时效签名 URL(如 5 分钟有效),前端通过 API 获取后立即使用。

7. 前端交互流程(用户视角)

  1. 用户进入句子详情页,点击“录制/上传音频”按钮。
  2. 使用浏览器 getUserMedia 或选择文件,上传至后端。
  3. 后端存入 R2,更新数据库,返回音频路径。
  4. 页面显示波形(Wavesurfer)和播放控制条。
  5. 用户可拖动波形设置 A/B 点,开启复读。
  6. 点击播放,音频从 R2 流式加载并播放。

8. 扩展性与未来

  • 若需支持多音频版本(如用户多次录制),可设计 audio_versions 表,并与句子关联。
  • 若需TTS 自动生成,可调用 Cloudflare AI(如 Workers AI)的文本转语音模型,但需注意费用,建议限定每日次数。

9. 小结

本方案完全基于 Cloudflare 免费资源,在现有 D1 + Worker 架构上平滑扩展,利用 R2 标准存储的免费出口流量,通过 Worker 代理实现安全、可缓存的音频播放,并集成了现代化的波形交互与 A-B 复读功能。整体设计遵循“简单、成本低、可扩展”的原则,与项目初衷高度一致。

如需具体代码片段或 Worker 路由配置细节,可进一步沟通。祝您的英语复习应用越做越好! 🚀