更改 1 对 1 webRTC Ably API 代码以处理群组语音通话

问题描述 投票:0回答:1

现在我使用 webRTC 和 Ably API 进行了完全正常的一对一语音通话。我想修改我的代码以某种方式创建一个房间(我不需要多个频道),这样当人们单击加入它时,他们将能够互相交谈。有什么想法怎么做吗?

我的 ively-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'
}

我的连接类:

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()}`)
    }
}
javascript webrtc call audio-streaming ably-realtime
1个回答
0
投票

主要问题是:你想快速实施还是正确实施?

  • 快速/错误的解决方案:每个加入的用户都给房间里的其他人打电话。代码复杂、客户端负载过大是不可避免的;
  • 正确的解决方案:在SFU或MCU模式下使用任何类型的媒体服务器(Janus、Jitsi、MediaSoup...)。或者使用任何支持音频 MCU 或 SFU 的 WebRTC Saas 平台。在这种情况下,每个用户将仅向服务器发送一个音频(和视频,如果需要),并且仅接收一个混合 (MCU) 或每个用户一个 (SFU) 流。

关键点是客户端的 CPU 负载 - WebRTC 独立编码每个传出媒体流,因此为其他对等点发送大量媒体流会消耗 CPU 资源。这就是为什么房间内有超过 4-5 个视频或 6-8 个纯音频用户的群组通话需要媒体服务器的原因。

© www.soinside.com 2019 - 2024. All rights reserved.