巧妙的 webrtc 群组通话 无法在 Javascript 和简单对等中工作

Posted

技术标签:

【中文标题】巧妙的 webrtc 群组通话 无法在 Javascript 和简单对等中工作【英文标题】:ably webrtc group call Not working in Javascript and simple peer 【发布时间】:2020-08-11 01:33:22 【问题描述】:

我们正在使用 Ably 和 simple peer 制作一个简单的群组通话网络应用程序。按照循序渐进的教程,一对一通话可以工作(在公共 ip 上使用时也有问题,但这是另一天的话题)

但是,当修改相同的代码以用于群组通话时,它似乎不起作用。列出了成员,用户可以加入群组(频道),控制台甚至显示信号已发送/接收。但音频视频流不起作用。以下是一段js代码(从巧妙的例子修改)

var membersList = []
var connections = 
var currentCall
var localStream
var CurrentGroup = ""
var constraints =  video: true, audio: true 
var apiKey = 'oZ_NdA.xyzabc' // a new app key for Group Call
    //var clientId = GetUserName() +'_'+ Math.random().toString(36).substr(2, 6) || 'client-' + Math.random().toString(36).substr(2, 16)
var clientId = GetUserName() || 'client-' + Math.random().toString(36).substr(2, 16)
let GroupMembers = null

let RealtimeApi = new Ably.Realtime( key: apiKey, clientId: clientId )
var RealtimeChannel;


function JoinGroup(groupName) 

    if (CurrentGroup !== "") 
        LeaveCurrentGroup()
    
    if (CurrentGroup == groupName) 
        // user is already in this group
        return
    

    CurrentGroup = groupName

    InitializeRealtimeChannel()
    RealtimeChannel.presence.enter()

    StartLocalCamera()
        //RegisterSignalReceivingEvent()



    RealtimeChannel.presence.subscribe('enter', function(member) 

        RealtimeChannel.presence.get((err, members) => 
            GroupMembers = members

            RenderGroupMemberList(groupName, GroupMembers)
            GenerateGroupMembersVideoTags(GroupMembers)
        )

        if (member.clientId === clientId) 
            document.querySelector("#GroupStatus").innerhtml = "You have Joined the Group"

            InitiateConnectionsWithGroupMembers(groupName)

            RealtimeChannel.subscribe(`rtc-signal/$clientId`, msg => 
                if (localStream === undefined) 
                    navigator.mediaDevices.getUserMedia(constraints)
                        .then(function(stream) 
                            /* use the stream */
                            localStream = stream
                            var video = document.getElementById('local')
                                //video.src = window.URL.createObjectURL(stream)
                            video.srcObject = stream
                            video.play()
                            connect(msg.data, stream)
                        )
                        .catch(function(err) 
                            alert('error occurred while trying to get stream')
                        )
                 else 
                    connect(msg.data, localStream)
                
            )

         else 
            document.querySelector("#GroupStatus").innerHTML = member.clientId + " has Joined"
        


    )

    RealtimeChannel.presence.subscribe('leave', member => 

        if (member.clientId === clientId) 
            document.querySelector("#GroupStatus").innerHTML = "You have Left the Group"
            document.querySelector("#GroupMemberVideos").innerHTML = ""
            connections = []

         else 
            document.querySelector("#GroupStatus").innerHTML = member.clientId + " has Left"
            ReleaseLeavingMemberResources(member.clientId)
        

        RealtimeChannel.presence.get((err, members) => 
            GroupMembers = members
            RenderGroupMemberList(groupName, GroupMembers)
        )

    )


function LeaveCurrentGroup() 
    if (CurrentGroup !== "") 
        InitializeRealtimeChannel()

        RealtimeChannel.presence.leave()
        CurrentGroup = ""
    


function GetAvailableMembersInGroup(groupName) 

    let Channel = RealtimeApi.channels.get(groupName)

    let GroupMembers = new Array()


    Channel.presence.get((err, members) => 
        GroupMembers = members

        RenderGroupMemberList(groupName, GroupMembers)

        // if (CurrentGroup == groupName) 
        //     GenerateGroupMembersVideoTags(members)
        // 

    )



function RenderGroupMemberList(groupName, memberNames) 

    let Element = document.querySelector("#" + groupName + " .people-list")
    let html = "<ul class='MemberList'>";

    if (!memberNames || memberNames.length < 1) 
        html += "<li style='font-weight: bold;'>No Member Yet..</li>"
    

    if (memberNames) 


        for (var i = 0; i < memberNames.length; i++) 

            if (memberNames[i].clientId !== clientId) 
                html += "<li>" + memberNames[i].clientId + "</li>"
             else 
                html += "<li style='font-weight: bold;'>" + memberNames[i].clientId + " (You)</li>"
            


        
    


    html += "</ul>"

    Element.innerHTML = html;


function GenerateGroupMembersVideoTags(memberNames) 

    let VideoHtml = "";

    if (memberNames) 

        for (var i = 0; i < memberNames.length; i++) 

            if (memberNames[i].clientId === clientId || document.querySelector("#VideoContainer_" + memberNames[i].clientId) != null) 
                // do nothing 
             else 

                let VideoTag = '<div class = "col-lg-3 col-sm-6 col-xs-12" id="VideoContainer_' + memberNames[i].clientId + '" >\n';
                VideoTag += '<video controls style = "width: 100%; height: 100%; min-height: 200px;" id="Video_' + memberNames[i].clientId + '" ></video> \n';
                VideoTag += '</div >';

                VideoHtml += VideoTag;
            
        
    


    // document.querySelector("#GroupMemberVideos").innerHTML += VideoHtml;
    document.querySelector("#GroupMemberVideos").insertAdjacentHTML("beforeend", VideoHtml)



function ReleaseLeavingMemberResources(client_Id) 
    if (document.querySelector("#VideoContainer_" + client_Id)) 
        document.querySelector("#VideoContainer_" + client_Id).remove();
    

    if (connections[client_Id]) 
        connections[client_Id] = null
        delete connections[client_Id]
    



function InitiateConnectionsWithGroupMembers(groupName) 


    if (GroupMembers) 
        for (let index = 0; index < GroupMembers.length; index++) 
            if (GroupMembers[index].clientId != clientId && connections[GroupMembers[index].clientId] == null)
                initiateCall(GroupMembers[index].clientId)
        
    




function StartLocalCamera() 
    navigator.mediaDevices.getUserMedia(constraints)
        .then(function(stream) 
            /* use the stream */
            localStream = stream
            var video = document.getElementById('local')

            if (video.srcObject == null) 
                video.srcObject = stream
                video.play()
            

            //video.src = window.URL.createObjectURL(stream)
        )
        .catch(function(err) 
            console.error(err)
            alert('Could not get video stream from source')
        )


function InitializeRealtimeChannel() 

    if (!RealtimeChannel) 
        RealtimeChannel = RealtimeApi.channels.get(CurrentGroup)
    



function initiateCall(client_id) 

    console.info(`Initialting call with $client_id`)

    // Create a new connection
    currentCall = client_id
    if (!connections[client_id]) 
        connections[client_id] = new Connection(client_id, RealtimeChannel, true, localStream)
    



function connect(data, stream) 
    if (!connections[data.user]) 
        connections[data.user] = new Connection(data.user, RealtimeChannel, false, stream)
    
    if (!connections[data.user].isConnected) 
        connections[data.user].handleSignal(data.signal)
    




function receiveStream(client_id, stream) 
    let video = document.getElementById('Video_' + client_id)
        //video.src = window.URL.createObjectURL(stream)
    video.srcObject = stream
    video.play()

以及我们下面的简单对等助手:

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) 
        InitializeRealtimeChannel()

        try 
            console.info("Sending Signal to :" + `$this._remoteClient`)

            RealtimeChannel.publish(`rtc-signal/$this._remoteClient`, 
                user: clientId,
                signal: signal
            )
         catch (error) 
            console.error("Signal error: " + error)
        
    
    _onConnect() 
        this.isConnected = true
        console.log('connected to ' + this._remoteClient)
    
    _onClose() 
        console.log(`connection to $this._remoteClient closed`)

    
    _onStream(data) 
        console.info("Attempting to receive stream from " + this._remoteClient)
        receiveStream(this._remoteClient, data)
    
    _onError(error) 
        console.log(`an error occurred $error.toString()`)
    

HTML 如下

<!--https://www.ably.io/tutorials/web-rtc-video-calling#testing-our-app-->

<!DOCTYPE html>
<html>

<head>
    <title>Group Calls</title>
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

</head>

<body class="bodybg">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.2.1/adapter.min.js"></script>
    <script src="https://cdn.ably.io/lib/ably.min-1.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/simple-peer/9.1.2/simplepeer.min.js"></script>
    <!-- <script src="js/simple-peer/simplepeer.min.js" type="javascript"></script> -->
    <script src="https://code.jquery.com/jquery-2.2.4.min.js" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

    <!-- <script src="js/simple-peer/index.js"></script> -->
    <script src="js/helper.js"></script>
    <script src="js/group-call.js"></script>
    <script src="js/ably-groupcall.js"></script>
    <!-- <script src="js/connection-helper.js"></script> -->
    <script src="js/group-connectionhelper.js"></script>







    <form action="groups.html" method="GET" id="DisplayNameForm">
        <div class="row" style="background-color:  rgba(136, 26, 32, 0.53);">

            <div class="col-sm-4">
                <div class="form-group">
                    <label style="color: #fff;">Display Name</label>
                    <input type="text" name="Name" class="form-control" />
                </div>
            </div>

            <div class="col-sm-2">
                <div class="form-group">
                    <label>&nbsp;</label>
                    <button type="submit" class="btn btn-primary btn-block">Update</button>
                </div>

            </div>

        </div>
    </form>

    <div class="conatainer-fluid" style="margin-top: 3em;">

        <div class="row">

            <!-- Group-1 -->

            <div class="col-sm-3 col-xs-6">
                <div class="panel panel-primary" id="Group_1">
                    <div class="panel-heading" id="Group_1_Heading">
                        Group-1
                    </div>
                    <div class="panel-body">
                        <h4>Members:</h4>
                        <div class="people-list"></div>
                    </div>
                    <div class="panel-footer">
                        <button onclick="JoinGroup('Group_1')" class="btn btn-info btn-block">Join</button>
                    </div>
                </div>
            </div>

            <!-- Group-2 -->

            <div class="col-sm-3 col-xs-6">
                <div class="panel panel-primary" id="Group_2">
                    <div class="panel-heading" id="Group_2_Heading">
                        Group-2
                    </div>
                    <div class="panel-body">
                        <h4>Members:</h4>
                        <div class="people-list"></div>
                    </div>
                    <div class="panel-footer">
                        <button onclick="JoinGroup('Group_2')" class="btn btn-info btn-block">Join</button>
                    </div>
                </div>
            </div>

            <!-- Group-3 -->

            <div class="col-sm-3 col-xs-6">
                <div class="panel panel-primary" id="Group_3">
                    <div class="panel-heading" id="Group_3_Heading">
                        Group-3
                    </div>
                    <div class="panel-body">
                        <h4>Members:</h4>
                        <div class="people-list"></div>
                    </div>
                    <div class="panel-footer">
                        <button onclick="JoinGroup('Group_3')" class="btn btn-info btn-block">Join</button>
                    </div>
                </div>
            </div>

            <!-- Group-4 -->

            <div class="col-sm-3 col-xs-6">
                <div class="panel panel-primary" id="Group_4">
                    <div class="panel-heading" id="Group_4_Heading">
                        Group-4
                    </div>
                    <div class="panel-body">
                        <h4>Members:</h4>
                        <div class="people-list"></div>
                    </div>
                    <div class="panel-footer">
                        <button onclick="JoinGroup('Group_4')" class="btn btn-info btn-block">Join</button>
                    </div>
                </div>
            </div>


        </div>


        <div class="row">
            <div class="col-sm-3 col-xs-12">
                <div class="panel panel-default">
                    <div class="panel-body">
                        <video id="local" muted style="z-index: 2; width: 100%; min-height: 200px;"></video>

                        <div class="row">
                            <div class="col-sm-12">
                                <div class="col-xs-6">
                                    <h3>Duration:</h3>
                                    <label id="minutes"></label> : <label id="seconds"></label>
                                </div>

                                <div class="col-xs-6">
                                    <button class="btn  btn-default" onclick="LeaveCurrentGroup()" title="Leave Group">
                                        <img src="asset/hang-up.svg" style="width: 50px; height: 50px; color: #fff;">
                                    </button>
                                </div>
                            </div>
                            <div class="col-sm-12">
                                <h3 id="CallStatus"></h3>
                            </div>

                            <div class="col-sm-12">
                                <h3 id="GroupStatus"></h3>
                            </div>

                        </div>

                    </div>
                </div>
                Icons made by <a href="https://www.flaticon.com/authors/dmitri13" title="dmitri13">dmitri13</a> from <a href="https://www.flaticon.com/" title="Flaticon"> www.flaticon.com</a>
            </div>


            <div class="col-sm-9 col-xs-12" style="margin-bottom: 50px;">

                <div id="call" style="position: relative;">
                    <div class="row" id="GroupMemberVideos">


                    </div>


                </div>

            </div>

        </div>


    </div>


</body>
<style>
    .MemberList 
        list-style: decimal;
    
</style>

</html>

<script type="text/javascript">
    window.onload = function() 

        var Name = GetUrlParameter('Name');


        if (Name != null && Name != '') 
            document.getElementById('DisplayNameForm').style.display = 'none'
        

        setTimeout(() => 
            UpdateGroupMemeberLists()
        , 5000);

    

    function UpdateGroupMemeberLists() 

        for (let index = 1; index <= 4; index++) 
            GetAvailableMembersInGroup("Group_" + index)
        

    
</script>

通过一些基本的调试,显示连接建立但没有流

任何帮助表示感谢,因为我完全不知道 webrtc 是如何工作的。

【问题讨论】:

嘿阿卜杜勒!这是 Srushtika - Ably 的开发倡导者。你也可以分享你的HTML文件吗?这将有助于调试问题可能是什么。我们可以尝试复制您遇到的确切问题。 @SrushtikaNeelakantam 。感谢您的答复。 HTML 已添加到问题中。任何帮助表示赞赏。 【参考方案1】:

发现问题。 :)

正在初始化加入组函数中的本地摄像机流。该问题已通过在文档加载时初始化本地视频流得到解决。

希望这对其他人有所帮助。

【讨论】:

以上是关于巧妙的 webrtc 群组通话 无法在 Javascript 和简单对等中工作的主要内容,如果未能解决你的问题,请参考以下文章

WebRTC 中的呼叫组视频

kurento android中的群组视频通话

React 原生 firebase+webrtc 进行视频通话

使用webrtc for android,如何在视频通话中保存图片?

从 webrtc 会话中提取视频流并转换为 rtmp

WebRTC简介(一)