将基于WebRTC与Ably API的一对一语音通话代码改造为群组通话
多人语音房间改造思路
基于你现有的一对一WebRTC+Ably代码,要改成单频道多人语音房间,核心是把主动呼叫的一对一P2P模式改成自动Mesh组网的多人P2P模式,以下是具体改造步骤:
一、移除一对一呼叫的交互与逻辑
现有代码里的呼叫请求、应答逻辑都是针对一对一场景的,需要全部移除:
- 删除
call()函数,以及incoming-call/${clientId}、call-details/${clientId}的订阅逻辑 - 移除
currentCall变量(不再需要跟踪单个通话对象) - 删除
initiateCall()里的currentCall = client_id赋值逻辑
二、改为用户加入房间后自动建立全员连接
利用Ably的Presence功能,在用户进入/离开时自动处理连接:
用户加入时自动连接所有在线成员
- 在
AblyRealtime.presence.subscribe('enter')的回调中,除了更新成员列表,对新加入的非当前用户的成员,直接创建Connection连接:AblyRealtime.presence.subscribe('enter', function(member) { AblyRealtime.presence.get((err, members) => { membersList = members renderMembers() // 主动连接新加入的成员(排除自己) if (member.clientId !== clientId && !connections[member.clientId]) { // 当前用户作为initiator发起连接 connections[member.clientId] = new Connection(member.clientId, AblyRealtime, true, localStream) } }) }) - 同时,当前用户自己进入房间时,要主动连接房间内已有的所有其他成员:
AblyRealtime.presence.enter((err) => { if (!err) { AblyRealtime.presence.get((err, members) => { membersList = members renderMembers() // 连接所有已在线的非自己成员 members.forEach(member => { if (member.clientId !== clientId && !connections[member.clientId]) { connections[member.clientId] = new Connection(member.clientId, AblyRealtime, true, localStream) } }) }) } })
- 在
处理新用户的连接请求
- 保留
rtc-signal/${clientId}的订阅逻辑,但修改connect()函数,确保每个远程用户只创建一个Connection实例,无需确认直接建立连接:function connect(data, stream) { if (!connections[data.user]) { connections[data.user] = new Connection(data.user, AblyRealtime, false, stream) } connections[data.user].handleSignal(data.signal) document.getElementById('call').style.display = 'block' }
- 保留
三、调整成员列表与UI逻辑
- 修改
renderMembers()函数,移除"call now"按钮,仅显示在线用户ID:function renderMembers() { var list = document.getElementById('memberList') var online = document.getElementById('online') online.innerHTML = 'Users online (' + (membersList.length === 0 ? 0 : membersList.length - 1) + ')' var html = '' if (membersList.length === 1) { html += '<li> No member online </li>' list.innerHTML = html return } for (var index = 0; index < membersList.length; index++) { var element = membersList[index] if (element.clientId !== clientId) { html += '<li><small>' + element.clientId + '</small></li>' } } list.innerHTML = html } - 调整
call元素的显示逻辑,只要有连接就显示,或者改成显示房间状态。
四、修改连接管理与流处理
本地流提前初始化
- 在页面加载或用户点击"加入房间"按钮时,提前获取本地音频流,无需等到呼叫时:
// 页面加载时初始化本地流 window.addEventListener('load', () => { navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { localStream = stream // 注意:原来设置volume=0会听不到自己的声音,多人场景下可以保留echoCancellation,但不需要静音自己 localStream.getAudioTracks().forEach(track => { track.enabled = true; // track.volume = 0; // 移除这行,否则自己听不到本地声音(如果需要监听自己可以保留,但通常不需要) }); }) .catch(function(err) { alert('Could not get audio stream from source') }) })
- 在页面加载或用户点击"加入房间"按钮时,提前获取本地音频流,无需等到呼叫时:
更新Connection类的关闭逻辑
- 修改Connection的
_onClose方法,不再调用handleEndCall(),而是直接清理对应连接和音频元素:_onClose() { console.log(`connection to ${this._remoteClient} closed`) // 移除对应的音频元素 const audioElement = document.getElementById(`audio-${this._remoteClient}`) if (audioElement) { audioElement.remove() } // 删除连接记录 delete connections[this._remoteClient] }
- 修改Connection的
修改receiveStream函数,为每个远程用户创建独立音频元素
- 避免重复创建,给每个音频元素设置唯一ID,并添加到页面中:
function receiveStream(client_id, stream) { // 检查是否已存在该用户的音频元素 let audio = document.getElementById(`audio-${client_id}`); if (!audio) { audio = new Audio(); audio.id = `audio-${client_id}`; audio.srcObject = stream; audio.autoplay = true; document.getElementById('call').appendChild(audio); } renderMembers() }
- 避免重复创建,给每个音频元素设置唯一ID,并添加到页面中:
重构handleEndCall为离开房间函数
- 新增
leaveRoom()函数,用于关闭所有连接并停止本地流:function leaveRoom() { // 关闭所有P2P连接 Object.values(connections).forEach(connection => { connection.destroy() }) connections = {} // 停止本地流 if (localStream) { for (var track of localStream.getTracks()) { track.stop() } localStream = undefined } // 离开Ably频道的presence AblyRealtime.presence.leave() document.getElementById('call').style.display = 'none' renderMembers() }
- 新增
五、处理用户离开的清理逻辑
- 在
AblyRealtime.presence.subscribe('leave')的回调中,销毁对应该用户的连接并清理音频元素:AblyRealtime.presence.subscribe('leave', member => { AblyRealtime.presence.get((err, members) => { membersList = members renderMembers() // 清理离开用户的连接 if (connections[member.clientId]) { connections[member.clientId].destroy() delete connections[member.clientId] } }) })
内容的提问来源于stack exchange,提问作者Anita Aksentowicz
相关产品推荐
相关产品推荐

