ably webrtc group call 在 Javascript 和简单对等体中不工作

ably webrtc group call Not working in Javascript and simple peer

我们正在努力使用 Ably 和简单的对等点制作一个简单的群呼网络应用程序。按照分步教程,一对一通话有效(在 public ip 上使用时也有问题,但这是另一天的主题)

但是,当相同的代码被修改为适用于群组通话时,它似乎不起作用。列出了成员,用户可以加入群组(频道),控制台甚至显示信号已 sent/received。但音频视频流不工作。以下是一些 js 代码(根据 ably 示例修改)

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 是如何工作的。

找到问题了。 :)

正在加入群组功能中初始化本地摄像机流。通过在文档加载时初始化本地视频流解决了该问题。

希望这对其他人有帮助。