先看效果:
智谱新上线的音视频通话大模型拥有理解音频和画面的能力,接入该api,需要使用webSocket协议不断传输音频和视频的base64编码。
WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音对话或视频对话的技术,webRTC最强大的功能是无需服务器,可以实现浏览器端对端的音视频通话,这里只使用了webRTC实现视频画面显示。
这里就详细说一下从开发到跑起这个demo踩的一些不得不说的血泪坑。
一
技术栈使用vue2,webRTC自然就选用了vue-webRTC,npm地址:https://www.npmjs.com/package/vue-webrtc/v/2.0.0?activeTab=readme
通过看它的readme文档发现,最新版是3.0.1,从3.0.0开始支持vue3,vue2使用的是2.0.0版本。
跑vue-webRTC这个示例没有出现问题,但是上线的时候却发现一直报错:Permissions policy violation: camera is not allowed in this document.
网上有关这些的文档都是说没有授权导致,在这里查资料卡了很久。
线上使用的也是https协议,也授权了权限,调试很多次一直报错,终于在一篇文档里发现了问题: 【Web安全】Permissions Policy(权限策略)详解
不知道是谁给线上环境的响应头里设置了不允许使用麦克风和摄像头,也是吐血了。
这里需要从Nginx修改配置,于是换了一个线上环境,成功解决。
二
智谱接入模型需要使用webSocket协议,这很简单,使用原生js new WebSocket(url)即可,提供了.send()方法发送数据,.onopen方法监听建立连接成功 .onmessage方法监听服务器返回。
部分代码如下:
this.ws = new WebSocket(this.socketURL + `?Authorization=${
APIKey}`)
this.ws.onopen = () => {
console.log('WebSocket 连接已打开')
}
this.ws.onmessage = event => {
let data = JSON.parse(event.data)
console.log('收到服务器消息:', data)
}
// 发送消息
this.ws.send(JSON.stringify(message))
但是接入的时候需要传入apiKey进行鉴权,api文档里写的是将key放在Header头里面,这完全就没有考虑浏览器调用情况!
浏览器端对写入header头很严格,而且这也不是简单的get post请求,可以用axios工具设置请求头。
所以当时有两种方法,1、找一个可以支持websocket协议写入请求头的库 2、使用@fortaine/fetch-event-source发送fetch请求,fetch是websocket的替代方案,可以解决传复杂参数问题。
因为有一个已经封装好了的可以用fetch的库,所以当时选择通过fetch协议调用。
通过一番操作以后,发post请求报405 发get请求报400,调试到这里真是吐血了!!用文档给的java代码调用就没有问题,这个文档根本就没有考虑浏览器方案。
服务器端只支持websocket协议,而websocket或eventsource是get请求,带参只能跟在url地址后面。所以发post会报405请求方法不对,后端又只从请求头里拿鉴权信息,发get带过去的它都不认。
经过一番协商,后端改了鉴权方法,从url地址里拿key信息,这下直接用原生webSocket协议都行了。
调试半天,后端改一下协议十分钟搞定。
三
搞定传输以后,剩下的就比较简单了,从音视频流里获取视频和音频数据,再转成base64编码,直接发送即可。
特别感谢:https://github.com/2fps/recorder
作者整理的关于音频的资料很详细。
请求获取音视频流:
// 获得音视频数据流
getUserMedia() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({
audio: true, // 设置为 true 以请求音频流
video: true
})
.then(stream => {
// 拿到音视频数据流
this.activeStream = stream
// 包含视频&&音频
this.videoStream = new MediaStream(stream)
// 创建只包含视频轨道的 MediaStream
// this.videoStream = new MediaStream()
// this.activeStream.getVideoTracks().forEach(track => this.videoStream.addTrack(track))
// 创建只包含音频轨道的 MediaStream
this.audioStream = new MediaStream()
this.activeStream.getAudioTracks().forEach(track => this.audioStream.addTrack(track))
// 获得音频数据流