极限3天:微信小程序实时语音对话 SDK 开发实战(基于 Coze API)

背景:为了挽留一个潜在客户

上个周末(周日),有个用户突然在 Github 上开了一个 Issue(uniApp的版本是否支持音频模式的回复?谢谢),之前已经完成基于 Web 的实时语音对话 SDK(基于 Coze Websocket Open API)。

说实话,一直很想在微信小程序上实现实时语音对话,有不少潜在的用户,之前基于火山 RTC 封装的 Web SDK,由于微信的限制,需要开通两个权限(live-player 和 live-pusher),这两个权限需要一定的资质才能申请,我们没法搞定这个,所以一直没法封装,导致用户只能通过 WebView 嵌 H5的方式使用,使用体验比较差。

火山的 RTC 是基于 WebRTC 的,它必须依赖微信提供的 live-player 和 live-pusher 这两个组件,现在扣子提供了 Websocket 版本的 API了,微信对 Websocket 的支持度还挺好的,用来实现实时语音对话也许可行,于是开始行动...

还有一个事项:如果让产品提需求做这个SDK,估计 ROI 太低提不上日程,最近有个需求前端UI已经开发差不多了,等后端联调,有几天的buffer,恰好可以来做这事,但时间也不会太多,于是周日下午就开始编码了,也希望能尽快能给用户提供一个 SDK

可行性分析:基本可行

用户是使用 UniApp SDK,所以先在这个 SDK 上实现支持,以下都是基于这个 SDK 开发的

目前要在小程序上实时语音对话,需要实现以下功能:

  1. 支持 Websocket API

    1. 这个之前用 axios 适配 HTTP 请求已经有了一些经验,可以采用 mixin 的方式来实现
    2. 另外微信小程序对 websocket 的支持度也挺高的,基本 API 能对齐
  2. 支持实时语音播放

    1. 难点主要在流式,也即音频是分 chunk 通过 WS 返回的,微信小程序提供了几种播放音频的方式,需要做下调研,看哪种方案可行
  3. 支持实时语音录制

    1. 微信小程序提供了 RecorderManager,它支持通过配置的方式实现实时回调事件,将音频片段发送给后端

以上这三个功能,两两组合,还能实现语音合成、语音识别这两个 SDK,于是在这次开发过程中,还希望实现这两个:

  1. Websocket + 实时流式语音播放 = 实时语音合成 SDK

  2. Websocket + 实时语音录制 = 实时语音识别(语音转写) SDK

  3. Websocket + 实时流式语音播放 + 实时语音录制 = 实时语音对话 SDK

说明一下:本次使用 Windsurf 来开发,之前没怎么开发过微信小程序应用,对各种 API 都不是很熟悉,如果没有 AI 编程辅助工具,也不可能敢接下这活,毕竟这代码量太大了,换做以前,这样的功能,没有十天半个月是不可能完成的

开发实战:一步填一坑

语音合成

实现源码位置:ws-tool/speech

先从最简单的开发,语音合成不涉及录音功能,可以在微信开发者工具跑起来,不用通过真机测试,比较方便,实现包括两部分,先对 Websocket 进行封装,然后再实现语音合成的功能

支持 Websocket API:顺利

实现源码位置:ws-tool/websocket

有了之前 Http 接口的适配,之前 Http 接口适配是采用混入的方式实现的,对外使用方式是一样的,只是改了一个包的路径(从 import { CozeAPI } from '@coze/api' 改为 import { CozeAPI } from '@coze/uniapp-api' ),也希望 Websocket 也是一样的,我先让 Windsurf 梳理一下现在基于 Http 的适配方案,然后让它参考现在的方式实现 Websocket 的适配,把相关上线文也带上。

总结:这一步还算顺利,生成的代码基本没啥问题,有些小问题手动改改就通了

支持流式语音播放

实现源码位置:ws-tool/pcm-stream-player

Websocket 返回的是 PCM 格式的音频数据,先去问下 DeepSeek,DeepSeek 给了几种实现方案,比较适合的只有先将 PCM 格式转成 wav 格式的,然后使用 uni.createInnerAudioContext() 来播放,它只能先将 wav 格式写入本地文件,然后再通过本地路径进行播放,这可能存在一个问题,就是在上一个音频和下一个音频之间可能存在极小的延迟,导致音频播放不连贯,刚开始有这个疑惑,不过也先试试看,最后跑起来了,在本地 IDE 上看起来还行,但是用手机测试的时候,延时就非常明显,期间做了不少优化,比如提前缓存音频资源,使用缓存对了,效果有提升但没法根本解决,只能放弃这个方案。

后来在与 DeepSeek 对话中,还发现有 uni.createWebAudioContext() 这个 API,这个 API 就跟原生的 WebAudioContext 很像,但不支持新版的 AudioWorkletNode 这种 worker 方式,只能使用最原始的方式了,果然,用这个就顺畅多了,效果也好很多。

另外还遇到微信小程序在iPhone静音模式下播放无声音的问题,这个 DeepSeek 也给了解决方案:播放一个静音包,大概示例如下代码:

javascript 复制代码
// 在播放前调用
wx.setInnerAudioOption({
  obeyMuteSwitch: false,  // 设置为false以忽略静音开关
  success: () => {
    console.log('配置成功,将忽略静音开关')
  },
  fail: (err) => {
    console.error('配置失败:', err)
  }
})

// 然后开始你的音频播放
this.startPlayback()

// 创建innerAudioContext实例
const innerAudioContext = wx.createInnerAudioContext()

// 配置忽略静音模式
innerAudioContext.obeyMuteSwitch = false

// 设置音频源(一个静音包,可以是一个 base64编码格式的)
innerAudioContext.src = 'xx'

// 播放音频
innerAudioContext.play()

不得不说,微信小程序相比原生浏览器,几乎所有 API 都重写了,这导致很多特性跟不上,或者只能用老的API,时间久了也不维护升级了,这某种程度上就很痛苦,导致一些功能要移植到小程序上就会有诸多限制与兼容,包括后面将要提到的回音无法消除,也是如此!

真机测试

最后一步就上真机测试,没想到这里却遇到一个坑,控制台出现 Connected Refused 问题,大概率猜到是域名没有配置白名单的问题,于是就去配置了白名单,没想到,配置了白名单后还是无法生效,各种地方找答案,大家的解决方案都是配置白名单,并没有其它思路,搞了很久,心灰意冷就去吃饭了,吃完饭回来,没想到突然可以了,啥都没改,估计是域名配置需要时间生效,但之前都没遇到这个问题,有点无语。
微信小程序的域名配置,可能需要一段时间才生效,算一个小坑吧

语音识别:坑少

源码位置:ws-tool/transcription

实现这个 SDK,先要封装通用的录音功能,可以实时将录音转成 PCM 格式,API 比较完善,相对简单

支持实时语音录制

源码位置:ws-tool/pcm-recorder

这一步,让 Windsurf 参考原来基于 Web 实现的 PcmRecorder,然后移除一些没有的功能,比如 AI 降噪相关的、输入设备选项(微信小程序不支持输入设备选项),然后就真的按照我的要求实现了,都几乎不用怎么改,太强了。

我发现, AI 工具的模仿能力超强,让它参考 xxx 的实现,然后就真的实现的 API 一模一样,只是换种语言语言/方案实现,完全都不用自己改

真机测试

录音这个功能只能在真机上测试了,幸好,一切顺利

语音对话:坑多

有了前面的铺垫,本以为这一步就简单了,无非就是好组合 pcm recorder 、 pcm player 和 websocket ,实现实时对话的功能,但事实并非如此,遇到不少问题。

坑一:编译问题

一开始遇到编译问题,由于引入原来 ws tools 的包,而它依赖了一些第三方库,而这些库不支持在微信小程序编译,需要 ignore ,刚开始根据 Windsurf 的建议增加一个配置,但是试了没有效果,编译不报错,但运行报错了,后来就一个个排查,最后发现是引入了一个枚举导致了,试了几种方案都没解决解决,最后只好将这个枚举拷贝一份了,问题就解决了。

小程序要引入一个新的库,真的慎之又慎,稍有不慎就遇到编译的问题,上面的问题,本来还想复用一下代码,最后还是放弃了,还是拷贝吧,少去折腾这些

坑二:音频播放卡顿问题

上面提到的 PcmStreamPlayer,在 IDE 上表现还算正常,但是到了真机上,就出现各种问题,在音频回复过程中,前面的音频会有卡顿的问题,初步排查结果是性能问题,音频的播放流程是这样的:

Base64 Pcm Data -> 转ArrayBuffer -> 重采样ReSample -> 播放onaudioprocess

由于微信小程序是单线程的,如果在处理音频播放的时候没有线程资源,那么就会导致播放卡顿,它用到的 API 是被浏览器淘汰的 API,没有支持新版的 Worker 方案(也即需要和主线程抢时间),所以只能通过代码的角度来优化了。

核心在于确保在处理音频播放的前后,线程能够空闲下来,于是需要对前面收到的 pcm data 进行缓存,只在合适的时机处理,经过不断地优化,现在播放的问题好多了,但在一些低端设备上,还是会有点点问题,这个后续还需要进一步优化。现在的处理流程是这样的: Base64 Pcm Data -> 检查线程是否空闲 -> 空闲 - > 转ArrayBuffer -> 重采样ReSample -> 缓存到内容

Base64 Pcm Data -> 检查线程是否空闲 -> 不空闲 - > 缓存到内容

播放onaudioprocess的时候,直接从缓存中读取数据

时间比较赶,后续应该还可以继续优化

坑三:回声消除问题

RecorderManager 这个录音功能本身是不支持回声消除的,相关问答:RecorderManager录音功能支持回声消除吗? , 感觉是微信故意不支持的,回声消除对于实现实时对话非常关键,如果没有回声消除这个功能,就无法同时使用扬声器和麦克风了,于是我这边做了两种策略,提供实时语音和按键说话的功能,实时语音需要带上耳机来体验,按键说话就跟现在的微信交互是一致的。

真机测试

最终效果如下:

最终结果:还算满意

这个 Demo源码在: coze-js-uniapp

经过 3 天的密集开发,中间还要应付开会、周会等时间,终于把版本发出去,如果没有 AI 工具,这事就不可能这么快完成,也估计不会去支持(因为这不是 OKR 的内容),但作为开源 SDK,如果无法应对社区提出的需求,那么就还不如不开源,既然开源了,还是要维护好社区,尽可能在不耽误工作的前提下。

最终得到用户的正面评价:

相关推荐
Leinwin2 小时前
微软开源GitHub Copilot Chat,AI编程领域迎新突破
microsoft·github·copilot
OEC小胖胖3 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水4 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
老虎06274 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
三水气象台4 小时前
用户中心Vue3网页开发(1.0版)
javascript·css·vue.js·typescript·前端框架·html·anti-design-vue
烛阴5 小时前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript
CN-Dust5 小时前
[FMZ][JS]第一个回测程序--让时间轴跑起来
javascript
草梅友仁5 小时前
草梅 Auth 与 AI 开发心得 | 2025 年第 27 周草梅周报
github·ai编程·视觉设计
全宝6 小时前
🎨前端实现文字渐变的三种方式
前端·javascript·css
yanlele7 小时前
前端面试第 75 期 - 2025.07.06 更新前端面试问题总结(12道题)
前端·javascript·面试