提问人:Anita Aksentowicz 提问时间:10/31/2023 更新时间:11/1/2023 访问量:30
更改 1 对 1 webRTC Ably API 代码以处理群组语音呼叫
Changing 1 on 1 webRTC Ably API code to handle group voice call
问:
现在,我使用 webRTC 和 Ably API 进行了完全有效的 1 对 1 语音通话。我想修改我的代码以某种方式创建一个房间(我不需要多个频道),这样当人们点击加入它时,他们将能够一起交谈。有什么想法怎么做吗?
我的 ably-videocall.js:
var membersList = []
var connections = {}
var currentCall
var localStream
var constraints = {video: false, audio: { echoCancellation: true}}
var apiKey = '0uLlaA.7H2Oow:P4nF0mGqCpOOmFtxNGPsctl5PGh8uTuCz1HPxf_yIfI'
var clientId = 'client-' + Math.random().toString(36).substr(2, 16)
var realtime = new Ably.Realtime({ key: apiKey, clientId: clientId })
var AblyRealtime = realtime.channels.get('ChatChannel')
AblyRealtime.presence.subscribe('enter', function(member) {
AblyRealtime.presence.get((err, members) => {
membersList = members
renderMembers()
})
})
AblyRealtime.presence.subscribe('leave', member => {
AblyRealtime.presence.get((err, members) => {
membersList = members
renderMembers()
})
})
AblyRealtime.presence.enter()
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 + ' <button class="btn btn-xs btn-success" onclick=call("' + element.clientId + '")>call now</button> </small></li>'
}
}
list.innerHTML = html
}
function call(client_id) {
if (client_id === clientId) return
alert(`attempting to call ${client_id}`)
AblyRealtime.publish(`incoming-call/${client_id}`, {
user: clientId
})
}
AblyRealtime.subscribe(`incoming-call/${clientId}`, call => {
if (currentCall != undefined) {
// user is on another call
AblyRealtime.publish(`call-details/${call.data.user}`, {
user: clientId,
msg: 'User is on another call'
})
return
}
var isAccepted = confirm(`You have a call from ${call.data.user}, do you want to accept?`)
if (!isAccepted) {
// user rejected the call
AblyRealtime.publish(`call-details/${call.data.user}`, {
user: clientId,
msg: 'User declined the call'
})
return
}
currentCall = call.data.user
AblyRealtime.publish(`call-details/${call.data.user}`, {
user: clientId,
accepted: true
})
})
AblyRealtime.subscribe(`call-details/${clientId}`, call => {
if (call.data.accepted) {
initiateCall(call.data.user)
} else {
alert(call.data.msg)
}
})
function initiateCall(client_id) {
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* use the stream */
localStream = stream
localStream.getAudioTracks().forEach(track => {
track.enabled = true; // Ensure the track is enabled
track.volume = 0; // Set volume to zero
});
// Create a new connection
currentCall = client_id
if (!connections[client_id]) {
connections[client_id] = new Connection(client_id, AblyRealtime, true, stream)
}
document.getElementById('call').style.display = 'block'
})
.catch(function(err) {
/* handle the error */
alert('Could not get video stream from source')
})
}
AblyRealtime.subscribe(`rtc-signal/${clientId}`, msg => {
if (localStream === undefined) {
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
/* use the stream */
localStream = stream
localStream.getAudioTracks().forEach(track => {
track.enabled = true; // Ensure the track is enabled
track.volume = 0; // Set volume to zero
});
connect(msg.data, stream)
})
.catch(function(err) {
alert('error occurred while trying to get stream')
})
} else {
connect(msg.data, localStream)
}
})
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'
}
function receiveStream(client_id, stream) {
var audio = new Audio();
audio.srcObject = stream;
audio.play();
renderMembers()
}
function handleEndCall(client_id = null) {
if (client_id && client_id != currentCall) {
return
}
client_id = currentCall;
alert('call ended')
currentCall = undefined
connections[client_id].destroy()
delete connections[client_id]
for (var track of localStream.getTracks()) {
track.stop()
}
localStream = undefined
document.getElementById('call').style.display = 'none'
}
我的 Connection 类:
class Connection {
constructor(remoteClient, AblyRealtime, initiator, stream) {
console.log(`Opening connection to ${remoteClient}`)
this._remoteClient = remoteClient
this.isConnected = false
this._p2pConnection = new SimplePeer({
initiator: initiator,
stream: stream
})
this._p2pConnection.on('signal', this._onSignal.bind(this))
this._p2pConnection.on('error', this._onError.bind(this))
this._p2pConnection.on('connect', this._onConnect.bind(this))
this._p2pConnection.on('close', this._onClose.bind(this))
this._p2pConnection.on('stream', this._onStream.bind(this))
}
handleSignal(signal) {
this._p2pConnection.signal(signal)
}
send(msg) {
this._p2pConnection.send(msg)
}
destroy() {
this._p2pConnection.destroy()
}
_onSignal(signal) {
AblyRealtime.publish(`rtc-signal/${this._remoteClient}`, {
user: clientId,
signal: signal
})
}
_onConnect() {
this.isConnected = true
console.log('connected to ' + this._remoteClient)
}
_onClose() {
console.log(`connection to ${this._remoteClient} closed`)
handleEndCall(this._remoteClient)
}
_onStream(data) {
receiveStream(this._remoteClient, data)
}
_onError(error) {
console.log(`an error occurred ${error.toString()}`)
}
}
答:
0赞
DMakeev
11/1/2023
#1
主要问题是:您想快速或正确地实现它吗?
- 快速/错误解决方案:每个加入的用户都会向房间中的其他人拨打电话。客户端的复杂代码和超负荷是不可避免的;
- 正确的解决方案:在SFU或MCU模式下使用任何类型的媒体服务器(Janus、Jitsi、MediaSoup等)。或者使用任何WebRTC Saas平台,支持音频MCU或SFU。在这种情况下,每个用户将只向服务器发送一个音频(和视频,如果需要),并且只接收一个混合 (MCU) 或每个用户一个 (SFU) 流。
关键点是客户端的 CPU 负载 - WebRTC 独立编码每个传出媒体流,因此为其他对等方发送大量媒体流会消耗 CPU 成本。这就是为什么房间内有超过 4-5 个视频用户或 6-8 个纯音频用户的群组通话需要媒体服务器的原因。
评论