实现音视频通话

更新时间: 2024/10/18 15:03:21

网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功初始化 SDK 之后,您可以简单体验本产品的基本业务流程。本文为您展示音视频通话提供的基本业务流程。

前提条件

请确认您已完成以下操作:

示例代码

网易云信为您提供完整的 实现基础音视频通话(包括创建界面)的示例代码作为参考,您可以直接拷贝用于运行测试。

单击展开查看实现音视频通话的示例代码。
HTML<html>
<head>
    <meta charset="UTF-8">
    <title>NERTC Video Call</title>
    <style type="text/css">
        * {
            font-family: sans-serif;
        }
        h1,
        h4 {
            text-align: center;
        }
        .container {
            text-align: center;
        }
        #localVideoContent, #remoteVideoContent {
            width: 640px;
            height: 480px;
            border: 1px solid #dfdfdf;
        }
        #localVideoContent {
            position: relative;
            margin: 0 auto;
            display: block;
        }
        #remoteVideoContent{
            display: flex;
            margin: auto;
            position: relative !important;
        }
    </style>
</head>
<body>
    <h1>
        NERTC Video Call
    </h1>
    <div class="container">
        <input type="number" id="channelName" name="房间名称" placeholder="房间名称" required>
        <input type="number" id="uid" name="UID" placeholder="uid" required>
        <button type="button" id="startCall">开始通话</button>
        <button type="button" id="finishCall">结束通话</button>
    </div>
    <h4>Local video</h4>
    <div id="localVideoContent"></div>
    <h4>Remote video</h4>
    <div id="remoteVideoContent"></div>
    <script src="NIM_Web_NERTC_x.x.x.js"></script>
    <script>
        let appkey = ''; // 请输入自己的 appkey
        let channelName ; // '您指定的房间号'
        let uid ; // '您指定的用户 ID'
        let client, localStream;
        document.getElementById("startCall").onclick = async function () {
        channelName = parseInt(document.querySelector('#channelName').value);
        uid = parseInt(document.querySelector('#uid').value);
        client = NERTC.createClient({appkey, debug: true})
        // 监听事件
        client.on('stream-added', event => {
            const remoteStream = event.stream;
            console.warn('收到别人的发布消息: ', remoteStream.streamID, 'mediaType: ', event.mediaType)
            //订阅远端流
            client.subscribe(remoteStream).then(()=> {
                console.warn(`subscribe 成功 ${remoteStream.streamID}`)
            });
        });
        client.on('stream-subscribed', event => {
            // 远端流订阅成功
            const remoteStream = event.stream;
            console.warn('订阅别人的流成功的通知: ', remoteStream.streamID, 'mediaType: ', event.mediaType)
            // 设置远端视频画布
            remoteStream.setRemoteRenderMode({
                width: 640,
                height: 480
            });
            // 播放远端流
            remoteStream.play('remoteVideoContent');

        });
        // 进房成功后开始推流
        try {
            await client.join({ channelName,uid });
            localStream = NERTC.createStream({ uid, audio: true, video: true, client});
            await localStream.init();
            // 设置本地视频画布
            localStream.setLocalRenderMode({
                width: 640,
                height: 480
            });
            // 播放本地流
            localStream.play("localVideoContent");
            await client.publish(localStream);
        } catch (error) {
            console.error(error);
        }
        }
        document.getElementById("finishCall").onclick = async function () {
        await client.leave();
        }

    </script>
</body>
</html>

背景信息

使用 SDK 实现音视频通话的主要流程如下图所示。

流程图

NERTC 中有两个重要类:

  • Client

    代表一个本地客户端。Client 类的方法提供了音视频通话的主要功能,例如加入房间、发布音视频流等。

  • Stream

    代表本地和远端的音视频流。Stream 类的方法用于定义音视频流对象的行为,例如流的播放控制、音视频的编码配置等。调用 Stream 方法时,请注意区分本地流和远端流对象。

第一步:创建音视频通话界面

您可以参考此步骤根据业务场景创建相应的音视频通话界面,若您已实现相应界面,请忽略该步骤。

实现基础的音视频通话,建议您参考 实现音视频通话的示例代码 在界面上添加以下控件。

  • 房间名称
  • 用户 ID
  • 本端视频窗口
  • 远端视频窗口
  • 开始通话按钮
  • 结束通话按钮

效果图如下图所示。

web 界面

第二步:引用文件

在项目相应的前端页面文件中,对 NIM_Web_NERTC_vx.x.x.js 文件进行引用。

  • import 方式引入:

    JavaScriptimport NERTC from '../../NIM_Web_NERTC_vx.x.x.js'
    
  • script 标签引入:

    JavaScript<script src="./NIM_Web_NERTC_vx.x.x.js"></script>
    

第三步:初始化

执行 createClient 方法创建 client 实例。

您需要将 appkey 替换为您的应用对应的 App Key。

JavaScript//创建 client 实例
rtc.client = NERTC.createClient({
  appkey: '<yourAppKey>', //您的 App Key
  debug: true, //是否开启调试日志
});

第四步:加入房间

加入房间前,请确保已完成初始化相关事项。若您的业务中涉及呼叫邀请等机制,可以使用 信令,总体实现流程请参考 一对一会话操作流程,具体呼叫邀请机制的实现请参考 邀请机制

调用 join 方法加入房间。

示例代码 如下:

JavaScript//加入房间
rtc.client.join({
    channelName: '房间名称',
    uid: uid,
    token: '' //调试模式下可设置为 null。正式上线前设置为相应的 Token,具体请参考 "Token 鉴权" 章节。
}).then((obj) => {
    console.info('加入房间成功...')
    //初始化本地流,并且发布
    initLocalStream() //后面介绍说明
})

参数说明

参数 说明
token 安全认证签名(NERTC Token)。
  • 调试模式下:可设置为 null。产品默认为安全模式,您可以在网易云信控制台将鉴权模式修改为调试模式,具体请参考 Token 鉴权
    调试模式的安全性不高,请在产品正式上线前修改为安全模式。
  • 产品正式上线后:请设置为已获取的 NERTC Token。安全模式下必须设置为获取到的 Token。若未传入正确的 Token 将无法进入房间。

    推荐使用安全模式

channelName 房间名称,长度为 1 ~ 64 字节。目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{|}~"。
设置相同房间名称的用户会进入同一个通话房间。
您也可以在加入通道前,通过 创建房间 接口创建房间。加入房间时,若传入的 {channelName} 未事先创建,则网易云信服务器内部将为其自动创建一个名为 {channelName} 的通话房间。
uid 用户的唯一标识 ID,为数字串,房间内每个用户的 uid 必须是唯一的。此 uid 为用户在您应用中的 ID,请在您的业务服务器上自行管理并维护。

为了实现标准音视频通话业务,您还需要在初始化时 注册相关必要回调,建议您在初始化方法中传入以下回调。

JavaScript//房间连接状态改变通知回调
rtc.client.on('connection-state-change', (evt)=>{
  console.log(`connection-state-change ${evt.prevState} => ${evt.curState}。是否重连:${evt.reconnect}`)
})
//远端用户加入房间通知回调,建议在收到此回调后再进行设置远端视图等的操作
rtc.client.on('peer-online', evt => {
  console.log(`${evt.uid} 加入房间`)
  addLog(`${evt.uid} 加入房间`)
})
//远端用户退出房间通知回调
rtc.client.on('peer-leave', evt => {
  console.log(`${evt.uid} 退出房间`)
  leaveLog(`${evt.uid} 退出房间`)
})
//远端用户推流/停止推流通知回调,建议在收到此回调后再进行订阅或取消订阅音视频流的操作
rtc.client.on("stream-added", (evt)=>{
  console.log(`远端${evt.stream.getId()}发布了 ${evt.mediaType} 流`)
  rtc.client.subscribe(evt.stream)
});
rtc.client.on("stream-removed", (evt)=>{
  // 远端流停止,则关闭渲染
  evt.stream.stop(evt.mediaType);
});
//网络质量通知回调(请列出所有枚举)
rtc.client.on('network-quality', stats => {
  console.log('=====房间里所有成员的网络状况:', stats)
  let status = null
  stats.forEach(item => {
    status = 'uid: ' + item.uid + ',上行:' + item.uplinkNetworkQuality + ',下行:' + item.downlinkNetworkQuality
    console.log(status)
  })
})

第五步:设置本地视图

初始化本地流成功后,可以设置本地视图,预览本地图像。

  1. 调用 getDevices 方法获取麦克风和摄像头设备的 deviceId。具体步骤请参考 音视频设备检测

  2. 加入房间后,调用 createStream 方法创建本地音视频流,并设置 deviceId

  3. 调用 initplay 方法初始化并播放本地音视频流,以预览本地图像。可以再通过 setLocalRenderMode 方法设置视频画面的渲染模式,例如视频宽、高和裁剪选项。

    在加入房间前,默认预览分辨率为 640*480,您可以通过 setVideoProfile 接口的 resolution 参数调整采集分辨率,但请在 initopen 之前调用。

  4. 可以调用 publish 方法发布自己的多媒体流至流媒体,供其他用户订阅。

    示例代码 如下:

    JavaScript//初始化本地流并且发布
    async function initLocalStream() {
    const cameras = await NERTC.getCameras();    //获取可用的视频输入设备
    const microphones = await NERTC.getMicrophones();     //获取可用的麦克风设备
    //创建本端 stream 实例,销毁前无需重复创建
    rtc.localStream = NERTC.createStream({
        uid: uid,                      // 本端的 uid
        audio: true,                   // 是否从麦克风采集音频
        microphoneId: microphones.microphoneId,    // 麦克风设备 deviceId,通过 getMicrophones() 获取
        video: true,                   // 是否从摄像头采集视频
        cameraId: cameras.cameraId             // 摄像头设备 deviceId,通过 getCameras() 获取
    })
    
    //启动本地音视频流,销毁前无需重复初始化
    rtc.localStream.init().then(()=>{
        console.warn('音视频初始化完成,播放本地视频')
        //用于播放视频的 div 元素
        let div = document.getElementById('local-container')
        //开始播放本地视频流
        rtc.localStream.play(div)
        //设置播放的视频容器大小
        rtc.localStream.setLocalRenderMode({
        width: 180,
        height: 150,
        cut: true    // 是否裁剪
        })
    
        // 将本地音视频流发布至网易云信服务器,加入房间前不用执行此方法。
        rtc.client.publish(rtc.localStream).then(()=>{
        console.warn('本地 publish 成功')
        })
    })
    }
    

第六步:设置远端视图

音视频通话过程中,除了要显示本地的视频画面,通常也要显示参与互动的其他连麦者/主播的远端视频画面。

在设置远端视图前,需要提前通过回调获取相关信息。在初始化完成后,通过注册以下回调获取远端用户的状态。

  • Client.on("peer-online"):监听远端用户加入通话房间的事件,并抛出对方的 uid。当本端加入房间后,也会通过此回调抛出通话房间内已有的其他用户。

    示例代码 如下:

    JavaScriptrtc.client.on('peer-online', evt => {
      console.log(`${evt.uid} 加入房间`)
      addLog(`${evt.uid} 加入房间`)
    })
    
  • Client.on("stream-added"):监听远端用户发布视频流的事件,回调中携带对方的 uid 与发布的视频分辨率。

    示例代码 如下:

    JavaScriptrtc.client.on('stream-added', evt => {
        var remoteStream = evt.stream;
        console.log('收到别人的发布消息: ', remoteStream.streamID, 'mediaType: ', evt.mediaType)
    })
    
  1. 在监听到远端用户发布视频流后,本端可以通过 subscribe 方法对其发起视频流的订阅,来将对方的视频流渲染到视频画布上。

    示例代码 如下:

    JavaScript//设置要订阅音频或者视频
    remoteStream.setSubscribeConfig({
        audio: true,//订阅麦克风音频
        audioSlave: true,//订阅音频辅流
        video: true,//订阅视频
        screenShare: true,//订阅屏幕共享
        highOrLow: NERTC.STREAM_TYPE.HIGH,//订阅大流
      })
    //发起订阅
    rtc.client.subscribe(remoteStream).then(()=>{
        console.log('发起订阅对端成功')
      })
    
  2. 订阅成功后,可进一步在 Client.on("stream-subscribed") 回调中调用 play 方法播放远端视频流。

    示例代码 如下:

    JavaScript//播放订阅的对端的音视频流
      rtc.client.on('stream-subscribed', evt => {
          console.warn('订阅别人的流成功的通知')
          var remoteStream = evt.stream;
          let div = document.getElementById('remote-container')
          //开始播放远端音视频流
          remoteStream.play(div).then(()=>{
            console.log('播放对端的流成功')
            remoteStream.setRemoteRenderMode({
              width: 180,
              height: 150,
              cut: true
            })
          })
        })
    

    需要特别注意的是,由于浏览器限制自动播放策略的影响,您需要在播放时引导用户手动恢复播放或直接解除自动播放受限,具体请参考 浏览器自动播放受限处理

第七步:退出通话房间

通过 leave 方法退出通话房间。

JavaScript  //用户无需做一些清除动作,SDK 会自动做清除逻辑
  async function leaveRoom() {
    try {
      // 使用 await 等待 leave 方法完成
      await rtc.client.leave();
      console.log('成功离开房间');
    } catch (error) {
      // 处理 leave 方法可能抛出的异常
      console.error('离开房间失败', error);
    }
  }

第八步:销毁实例

当确定短期内不再使用音视频通话实例时,可以调用 destroy 方法释放对应的对象资源。

JavaScript  //一般情况下无需使用
  rtc.client.destroy()

常见问题

为什么我订阅远端用户发布的流后听不到声音?

NERTC Web SDK 要求您在成功订阅远端发送的音视频流之后调用 play 方法播放,因此您需要在接收到的 stream-subscribed 回调(表示订阅远端的音/视频流成功)里,调用 play 方法播放远端的音视频流,网易云信推荐您此时将 audio 和 video 参数均设置为 true。

为什么我离开房间后,摄像头仍处于使用状态?

您可能重复调用了 Stream.init 方法初始化音视频流对象,导致您在本地创建了两条不同的流,同时在使用摄像头设备。因此在您调用 leave 方法离开音视频房间时,只结束了其中一条流的设备采集任务,致使摄像头仍处于使用状态。

建议您在实际业务场景中,需管理好 Stream.init 方法的调用次数及 Stream 对象的生命周期,确保每个 Stream 只被初始化一次,以确保音视频的正常使用。

此文档是否对你有帮助?
有帮助
去反馈
  • 前提条件
  • 示例代码
  • 背景信息
  • 第一步:创建音视频通话界面
  • 第二步:引用文件
  • 第三步:初始化
  • 第四步:加入房间
  • 第五步:设置本地视图
  • 第六步:设置远端视图
  • 第七步:退出通话房间
  • 第八步:销毁实例
  • 常见问题
  • 为什么我订阅远端用户发布的流后听不到声音?
  • 为什么我离开房间后,摄像头仍处于使用状态?