提示

时信魔方最新版本是 3.0 ,我们正全力推进 3.0 版本的开发,目前的 2.5、2.7 版本转入维护状态。如果您需要使用其他发行版本,建议您到 时信魔方官方站点 了解详情。

如果您需要的是一款成熟的社交协作产品,可以使用 司派 ,这是一款已经上线的产品。

Web/HTML5 - 群组语音通话

本示例较之前的示例程序复杂,因此建议您先阅读 视频通话群组管理 再阅读本示例。

时信魔方 里使用 MultipointComm 模块进行多方通讯。这个示例里我们将使用 Group 进行群组成员之间的多人通话,这样便于群组成员随时发起通话邀请。

在开始讲解前我们需要先清楚以下几个概念:

  • 魔方引擎里 CommField 表示一个“场域”,一个场域就是若干个通讯终端节点的集合和控制中心,每个通讯终端用 CommFieldEndpoint 表示。

  • 每个终端参与通话时,上行(出站)数据和下行(入站)数据分离,且每个终端有且仅有一路上行连接和一路下行连接。

  • 通话控制策略影响媒体服务器对数据流的控制策略,由服务器控制终端在场域里的数据行为。终端的媒体控制操作不影响服务器上的媒体流控制。


快速开始

在开始运行程序之前您需要在您的设备上连接麦克风设备,例如使用 USB 进行连接的网络麦克风或者您的笔记本电脑内置的麦克风。

按照以下步骤运行演示程序:

  1. 安装 时信魔方 服务器:服务器安装指南

  2. 执行以下命令运行演示程序:

    git clone https://gitee.com/shixinhulian/cube-javascript
    cd cube-javascript/cube
    npm install
    npm run example
    
  3. 打开浏览器(Chrome 或 Firefox)输入:http://localhost:8000/examples/group-audio/https://localhost:4430/examples/group-audio/ 进入演示程序界面。

  4. 选择一个演示账号,点击“登录”按钮。

  5. 点击“发起群组通话”按钮,并允许开启麦克风,将发起或加入群组通话。

  6. 点击“加入群组通话”按钮,并允许开启麦克风,将加入群组通话。

群组语音通话

群组语音通话

在通话过程中,在“参与者”姓名后的输入框里可以实时看到对方麦克风的音量显示。如果需要查看 RTP 统计数据信息,点击“显示/隐藏连接数据”,如下图所示:

群组语音通话 RTP 数据统计

群组语音通话 RTP 数据统计


工作原理

示例的基本工作原理:终端(浏览器)申请为指定的群组创建 CommField 进行群组通信,然后启动 WebRTC 创建上行通道向服务器发送本地麦克风的数据流,接着创建下行通道服务器将场域内混码后的语音数据流发给给终端。

媒体服务器 MCU 数据流

媒体服务器 MCU 数据流


理解示例程序

  1. 准备事件监听。除了通话的流程事件:CommEvent.InProgressCommEvent.RingingCommEvent.ConnectedCommEvent.Bye,我们还需要增加监听以下几个事件:

    • CommEvent.Arrived - 当有新的成员加入通话时该事件被触发。触发事件调用 onArrived() 函数刷新“参与者”列表。

    • CommEvent.Left - 当成员退出通话时该事件被触发。触发事件调用 onLeft() 函数刷新“参与者”列表。

    • CommEvent.MicrophoneVolume - 当成员麦克风音量改变时该事件被触发。触发事件调用 onMicrophoneVolume() 函数显示成员的麦克风音量数据。

  2. 选择一个可用的设备。当我们的主机上安装了多个麦克风设备时,我们可以使用 MediaDeviceTool.enumDevices() 方法来枚举已经安装的设备,并选择其中一个设备使用,如下图所示:

    选择麦克风设备

    选择麦克风设备

    在示例里我们使用 helper.js 里的 selectMediaDevice() 函数显示一个设备列表来进行选择操作。在发起通话前,设置媒体约束时将选择的设备赋值给媒体约束对象:

    // 创建媒体约束,使用音频,禁用视频
    var mediaConstraint = new MediaConstraint(false, true);
    // 设置设备
    mediaConstraint.setAudioDevice(device);
    
  3. 使用 video 标签播放通话语音数据,在 index.html 里加入 video 标签:

    <div id="media-container">
        <video id="local" autoplay></video>
        <video id="remote" autoplay></video>
    </div>
    

    在发起通话前将标签 DOM 实例赋值给魔方引擎:

    cube.mpComm.setLocalVideoElement(document.querySelector('video#local'));
    cube.mpComm.setRemoteVideoElement(document.querySelector('video#remote'));
    
  4. 发起/加入通话和退出通话,依次使用以下方法:

    • makeCall() - 使用这个方法传入 Group 实例即可发起通话,同样的,加入通话也是调用该方法。

    • hangupCall() - 使用这个方法从当前的通话中退出。

  5. 控制麦克风数据。CommFieldEndpoint 提供了对麦克风设备数据的控制操作:

    • isAudioMuted() - 用于判断麦克风是否被静音。

    • muteAudio() - 将麦克风静音,即设备不再传输麦克风数据到服务器。

    • unmuteAudio() - 将麦克风恢复,即设备恢复将麦克风数据传输到服务器。


    这里需要注意的是,只有对当前登录的联系人对应的 CommFieldEndpoint 进行操作才能控制本地的媒体设备, 在任何情况下,您都无法控制别人的媒体设备 。使用以下代码获得自己的本地终端对象实例:

    cube.mpComm.getActiveField().getEndpoint();
    
  6. 获取 RTP 统计数据。这是一个 可选操作,如果您对 RTP 数据不感兴趣可以跳过这节内容。

    CommField 对象提供了 snapshootStatsReport() 函数用于快照当前的 RTP 状态报告:

    cube.mpComm.getActiveField().snapshootStatsReport(function(field, stats) {
        [...]
    }, function(field, stats) {
        [...]
    });
    

代码详解

示例里我们使用的文件有:

  • index.html :主页面文件。

  • main.css :页面样式表文件。

  • main.js :示例的程序主文件。包括程序逻辑流程和页面事件处理。

  • helper.js :提供一些帮助函数的库文件。

  • cube.js :时信魔方的 JavaScript 客户端库文件。

  1. 监听魔方引擎事件:

    // 监听 SignIn 事件
    cube.contact.on(ContactEvent.SignIn, onSignIn);
    
    // 监听通话相关事件
    cube.mpComm.on(CommEvent.InProgress, onInProgress);
    cube.mpComm.on(CommEvent.Ringing, onRinging);
    cube.mpComm.on(CommEvent.Connected, onConnected);
    cube.mpComm.on(CommEvent.Bye, onBye);
    
    // 监听参与人事件
    cube.mpComm.on(CommEvent.Arrived, onArrived);
    cube.mpComm.on(CommEvent.Left, onLeft);
    
    // 监听麦克风音量事件
    cube.mpComm.on(CommEvent.MicrophoneVolume, onMicrophoneVolume);
    
  2. 为界面上 UI 控件绑定事件:

    btnLogin.onclick = login;
    btnLogout.onclick = logout;
    btnInitiate.onclick = initiate;
    btnJoin.onclick = join;
    btnQuit.onclick = quit;
    btnSwitchMic.onclick = switchMic;
    btnStatistics.onclick = statistics;
    
  3. 账号签入之后创建一个用于群组通话的群组。为了便于示例演示,我们约定创建一个名为“ 群组语音通话演示群 ”的群组将所有演示账号加入到群组里,这样使用演示账号签入之后就可以进行群组通话了。

    function onSignIn(event) {
        [...]
    
        var groupName = '群组语音通话演示群';
        var currentGroup = null;
    
        // 查询与该账号有关的所有群组
        cube.contact.queryGroups(function(list) {
            // 查找是否有名称匹配的群组
            for (var i = 0; i < list.length; ++i) {
                var group = list[i];
                if (group.getName() == groupName) {
                    currentGroup = group;
                    break;
                }
            }
    
            if (null == currentGroup) {
                // 创建新群组
                cube.contact.createGroup(groupName, getAllContactsId(), function(group) {
                    [...]
                    groupId = group.getId();
                });
            }
            else {
                // 已经有这个群组,不需要创建
                [...]
                groupId = currentGroup.getId();
            }
        });
    }
    
  4. 发起群组通话:

    function initiate() {
        // 选择媒体设备
        selectMediaDevice('audio', function(device) {
            [...]
    
            // 设置媒体元素
            cube.mpComm.setLocalVideoElement(document.querySelector('video#local'));
            cube.mpComm.setRemoteVideoElement(document.querySelector('video#remote'));
    
            // 创建媒体约束,使用音频,禁用视频
            var mediaConstraint = new MediaConstraint(false, true);
            // 设置设备
            mediaConstraint.setAudioDevice(device);
    
            // 获取指定的群组
            cube.contact.getGroup(groupId, function(group) {
    
                // 使用指定的群组发起通话
                cube.mpComm.makeCall(group, mediaConstraint, function(activeCall) {
                    [...]
                }, function(error) {
                    [...]
                });
            });
        });
    }
    

    函数 selectMediaDevice()helper.js 文件提供,以对话框方式提示选择设备,如果只发现一个可用设备则不会显示对话框。

    创建不使用视频,仅使用音频通道的 MediaConstraint 媒体约束,并设置设备。

    ContactService 模块获得 Group 实例之后,将 Group 实例传递给 makeCall() 发起群组通话。当然加入群组通话也是调用 makeCall() 方法,这在后面会讲到。

  5. 加入群组通话:

    function join() {
        // 获取指定的群组
        cube.contact.getGroup(groupId, function(group) {
    
            // 判断群组是否正在进行通话
            cube.mpComm.isCalling(group, function(calling) {
                if (!calling) {
                    alert('当前群组没有正在进行的语音通话');
                    return;
                }
    
                // 如果群组正在通话,则执行 initiate() 函数
                initiate();
            });
        });
    }
    

    如前所述加入群组通话也是调用 makeCall() 方法。在示例里代码里,我们演示了使用 isCalling() 来判断指定的群组是否正在进行群组通话。

  6. 退出群组通话:

    function quit() {
        [...]
    
        // 挂断当前的通话
        cube.mpComm.hangupCall(function() {
            [...]
        }, function(error) {
            [...]
        });
    }
    

    在任何时候退出通话都可以调用 hangupCall() 来操作。退出群组通话也是如此。

  7. 处理麦克风音量事件:

    function onMicrophoneVolume(event) {
        var endpoint = event.data.endpoint;
        var volume = event.data.volume;
    
        if (endpoint.contact.id == cube.contact.getSelf().id) {
            var num = Math.round(volume * 0.2);
            var chunk = [];
            for (var i = 0; i < num; ++i) {
                chunk.push('■');
            }
            inputMicVolume.value = chunk.join('');
        }
    
        // 更新麦克风音量显示
        refreshMicVolume(endpoint, volume);
    }
    

    魔方引擎的麦克风音量值是从 0100 的数值,数值越大表示麦克风输出的音量越大。为了便于显示示例程序将音量值乘于 0.2 将数值划分为五个区间显示,即 0 到 20 显示一个方块( ) ,20 到 40 显示两个方块( ■■ ),依此类推。