Android

实现音视频通话

更新时间: 2024/06/07 14:47:50

网易云信音视频通话产品的基本功能包括高质量的实时音视频通话。当您成功集成并初始化 SDK 之后,您可以简单体验本产品的基本业务流程。本文介绍如何通过小程序 Demo 实现音视频通话的基本业务流程。

GitHub 项目

网易云信在 GitHub 上提供以下开源的小程序多人通话示例项目 netease-kit/NEGroupCall。在搭建自己的小程序项目前,您可以下载体验或参考源代码。

Demo 体验

网易云信为您提供微信小程序的多人通话 Demo。您可以在手机上安装微信 App,通过微信扫描识别以下二维码,快速体验网易云信的微信小程序音视频通话业务场景。

G2-MiniAppDemo02

前提条件

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

同时建议您阅读微信小程序的官方文档,了解微信小程序的基本开发要点。live-pusherlive-player 是腾讯微信小程序中用于实现直播功能的两个组件,它们分别负责推流(发送直播视频流)和拉流(接收并播放直播视频流)。详情请访问《微信小程序文档》live-pusherlive-pusherwx.createLivePusherContext

  • live-pusher 用于发送直播视频流,通常在需要开始直播推流的页面加载时初始化。
  • live-player 用于播放直播视频流,其调用时机取决于用户想要观看直播的时刻。

实现音视频通话

1. 初始化客户端对象

调用 YunXinMiniappSDK.Client 方法创建一个用于控制通话的客户端对象,然后调用 init() 进行初始化。

let client = YunXinMiniappSDK.Client({
    debug: true,
    appkey: ''
});

client.init();

2. 加入房间

调用 client.join 方法加入房间。

client.join 中您可以将 yourToken 替换成已获取的 NERTC Token。并填入想要加入的房间名以及用户 uid。

NERTC SDK 支持用户角色管理,角色包括主播(broadcaster)和观众(audience),默认以主播角色加入房间。加入之后,可以通过 setRole() 切换用户角色为观众。如果在主播已 publish 的状态下调用该方法将用户角色设置为观众,会导致之前的推流地址无效。

let joinParam = {
    channelName: '',
    uid: 100, //支持超出 number 精度范围的 uid。uid 超过了 number 范围,可以以 string 的方式传入 sdk,sdk 会当做 BigNumber 类型(bignumber.js)处理。uid 超过了 number 范围,也可以直接传入 BigNumber 类型(bignumber.js)。请参考 API 文档。
    token: 'yourToken', //安全模式下必填,设为已获取的 NERTC Token。调试模式下,请勿设置 Token。
    liveEnable: 0, //是否开启互动直播
    recordType: '0', //以下为服务器录制相关参数,请参考 API 文档
    recordAudio: 0,
    recordVideo: 0,
    isHostSpeaker: 0,
  }
  client.join(joinParam).then(data => {
     console.log('!!!! 房间房间成功:')
  }).catch(e => {
     console.error('!!!! 房间房间失败:', e)
  })

3. 发布本地流

成功加入房间后,就能调用 client.publish 方法将本地音视频流发布到房间中。成功发布后,SDK 会返回该路音视频流的 URL。

let mediaType = ''//可以设置为'audio'或者'video'。如果是空字符串'',表示同时发布 audio 音频流和 video 视频流
client.publish().then(url => {
    console.log('推流成功, 获取推流地址: ', url);
    //业务层将推流 url 设置的 live-pusher 组件中
}).catch(e => {
    console.log('推流失败,原因: ', e);
})

4. 订阅远端流

当远端流发布到房间时,会触发 stream-added 事件,您需要通过 client.on 监听该事件并在回调中订阅新加入的远端流。

client.on('stream-added', ({uid, mediaType, isBigNumber}) => {
    //如果对端 uid 超出了 number 范围,isBigNumber 值为 true,uid 为 String 类型,否则 isBigNumber 为 false,uid 为 number 类型
    console.log(`${uid} 发布了自己的 ${mediaType} 数据 `)
    client.subscribe(uid, mediaType).then(data => {
        logger.log('订阅别人成功,获取到拉流地址: ', data.url)
        //业务层将拉流 url 设置的 live-player 组件中
    }).catch(e => {
        logger.log('订阅别人失败,原因: ', e)
    })
})

当远端用户取消发布流或退出房间时,关闭并移除对应的流。

client.on("stream-removed", {uid, mediaType} => {
  console.log(`[stream-removed 通知] ${uid} 停止发布自己的 ${mediaType}`)
  //业务层做停止推流动作,清除响应的 live-player 组件
});

5. 事件通知

SDK 内部做了重连相关的逻辑,中途会有一些事件通知到业务层,此外也有一些业务事件会反馈,建议根据自己的业务合理利用。

  • 通知应用程序更新后的推流地址和拉流地址。
    //通知应用程序更新后的推流地址和拉流地址。
    client.on('syncDone', (data) => {
      const {uid, url, isBigNumber} = data
      console.log('[syncDone 通知] 推流地址发生了变化, data: ', data)
      //重新设置 live-pusher 组件的推流 url
    })
    
  • 通知成员状态。
    //通知应用程序有人离开房间。
    client.on('clientLeave', (data) => {
      const {uid, isBigNumber} = data
      console.log('音视频通知:有人离开了,清除拉流的 live-player 组件')
    })
    
    //通知应用程序有人加入房间。
    client.on('clientJoin', (data) => {
      const {uid, isBigNumber} = data
      console.log('音视频通知:有人加入')
    })
    
    //通知应用程序自己被踢出。
    client.on('kicked', (data) => {
      console.log('音视频通知:被踢')
    })
    
  • socket 状态通知。
    //通知应用程序 socket 建立成功。
    client.on('open', (data) => {
    console.log('音视频通知:和服务器 socket 建立成功')
    })
    
    //通知应用程序音视频 socket 关闭。
    client.on('disconnect', (data) => {
    console.log('音视频通知:和服务器 socket 关闭了')
    })
    
    //通知应用程序准备重连。
    client.on('willreconnect', (data) => {
    console.log('音视频通知:准备重新建立和服务器之间的联系')
    })
    
    //通知应用程序 SDK 信令发送超时。
    client.on('sendCommandOverTime', (data) => {
    console.log('音视频通知:sdk 信令发送超时')
    })
    
    //通知应用程序房间被解散。
    client.on('liveRoomClose', (data) => {
    console.log('音视频通知:房间解散了')
    })
    

6. 监听质量数据

小程序 SDK 的推流和拉流,主要依赖小程序平台的 live-pusherlive-player 组件,因此想要获取小程序实时媒体的收发情况,需用业务层主动去监听小程序的回调事件。

live-pusher 组件监听事件

请参考小程序文档查看详细信息。

其中:

  • bindstatechange:状态变化事件
  • bindnetstatus:网络状态通知
  • binderror:渲染错误事件
XML<live-pusher
    wx:if="{{rtmpUrl!==''}}"
    wx:key="livePusher"
    style="display:inline-block;width:180px;height:150px;"
    aspect="16:9"
    mode="RTC"
    enable-agc="true"
    enable-ans="true"
    orientation="vertical"
    url="{{rtmpUrl}}" //推流地址,client.publish()接口可以获取
    muted="false"
    enable-camera="true"
    beauty="0"
    waiting-image="../../images/cover.png"
    max-bitrate="500"
    min-bitrate="200"
    bindstatechange="pusherStateChangeHandler"
    bindnetstatus="pusherNetstatusHandler"
    binderror="pusherErrorHandler"
    debug="true"
    autopush="true"
>

您可以调用 SDK 的数据上报接口,实时传递给 SDK 这些数据,可以帮助 SDK 进行问题排查、质量优化。也可以自己监听这些信息,进行进一步的业务处理。

示例代码

JavaScriptfunction pusherStateChangeHandler(data) {
    let { code } = data.detail
    client.dataReporter('push', 'bindstatechange', {
      code,
      reason: '' //状态码的说明,小程序 live-pusher 组件文档有详细说明
    })
}

function pusherNetstatusHandler(data) {
    let { info } = data.detail
    client.dataReporter('push', 'bindnetstatus', info)
}

function pusherErrorHandler(data) {
    let { errMsg, errCode } = data.detail
    client.dataReporter('push', 'binderror', {
      errCode,
      errMsg
    })
}

live-player 组件监听事件

请参考小程序文档查看详细信息。

其中:

  • bindstatechange:播放状态变化事件
  • bindnetstatus:网络状态通知
XML<live-player
    bindtap='videoClickHandler'
    style="display:inline-block;width:180px;height:150px;"
    data-user="{{uid}}" //可以设置成 client.subscribe(uid)中的 uid
    wx:if="{{item.uid !== uid}}" //要记得及时清除不存在的拉流地址
    src="{{item.url}}" //client.subscribe(uid)订阅成功的拉流地址
    mode="RTC"
    debug="true"
    min-cache="0.2"
    max-cache="0.8"
    auto-pause-if-navigate= "true"
    auto-pause-if-open-native= "true"
    bindstatechange="pullerStateChangeHandler"
    bindnetstatus="pullerNetstatusHandler"
    autoplay="true"
    >

您可以调用 SDK 的数据上报接口,实时传递给 SDK 这些数据,可以帮助 SDK 进行问题排查、质量优化。也可以自己监听这些信息,进行进一步的业务处理。

示例代码

JavaScriptfunction pullerStateChangeHandler(data) {
    let { code } = data.detail
    client.dataReporter('pull', 'bindstatechange', {
      code,
      reason: '' //状态码的说明,小程序 live-player 组件文档有详细说明
    })
}

function pullerNetstatusHandler(data) {
    let { info } = data.detail
    client.dataReporter('pull', 'bindnetstatus', info)
}

7. 离开房间

调用接口 Client.leave() 离开房间。

运行项目

为保证运行效果,请务必在真机上运行项目。完成开发后,单击微信开发者工具界面的真机调试。扫描生成的二维码,即可在手机端运行和调试项目。

Sample code

您可以直接参考如下示例代码,在项目中实现想要的功能。

JavaScriptimport YunXinMiniappSDK from '../../sdk/NIM_Web_Netcall_weixin_G2.js'

let client = YunXinMiniappSDK.Client({
    debug: true,
    appkey: ''
});

client.init();

//监听事件通知
initEvent()

let joinParam = {
    channelName: '',
    uid: 100,
    liveEnable: 0,
    recordType: 0,
    recordAudio: 0,
    recordVideo: 0,
    isHostSpeaker: 0,
}
client.join(joinParam).then(data => {
    console.log('!!!! 房间房间成功:')
    client.publish().then(url => {
        console.log('推流成功, 获取推流地址: ', url);
        //业务层将推流 url 设置的 live-pusher 组件中
    }).catch(e => {
        console.log('推流失败,原因: ', e);
    })
}).catch(e => {
    console.error('!!!! 房间房间失败:', e)
})

function initEvent() {
    client.on('stream-added', ({uid, mediaType}) => {
        console.log(`${uid} 发布了自己的 ${mediaType}`)
        client.subscribe(uid, mediaType).then(data => {
            console.log('订阅别人成功,获取到拉流地址: ', data.url)
            //业务层将拉流 url 设置的 live-player 组件中
        }).catch(e => {
            console.log('订阅别人失败,原因: ', e)
        })
    })
    //通知应用程序更新后的推流地址和拉流地址。
 client.on('syncDone', (data) => {
  const {uid, url, isBigNumber} = data
  console.log('[syncDone 通知] 推流地址发生了变化, data: ', data)
 })

 //通知应用程序有人离开房间。
 client.on('clientLeave', (data) => {
   console.log(data.uid) //离开的成员列表
   console.log('音视频通知:有人离开了,清除拉流的 live-player 组件')
 })

 //通知应用程序有人加入房间。
 client.on('clientJoin', (data) => {
   const {uid} = data
   console.log('音视频通知:有人加入')
 })

 //通知应用程序自己被踢出。
 client.on('kicked', (data) => {
   console.log('音视频通知:被踢')
 })

 //通知应用程序 socket 建立成功。
 client.on('open', (data) => {
   console.log('音视频通知:和服务器 socket 建立成功')
 })

 //通知应用程序音视频 socket 关闭。
 client.on('disconnect', (data) => {
   console.log('音视频通知:和服务器 socket 关闭了')
 })

 //通知应用程序准备重连。
 client.on('willreconnect', (data) => {
   console.log('音视频通知:准备重新建立和服务器之间的联系')
 })

 //通知应用程序准备重连。
 client.on('reconnected', (data) => {
    console.log('音视频通知:已经重新建立和服务器之间的联系')
    //建议用户刷新 live-pusher 组件
 })

 //通知应用程序 sdk 信令发送超时。
 client.on('sendCommandOverTime', (data) => {
   console.log('音视频通知:sdk 信令发送超时')
 })

 //通知应用程序房间被解散。
 client.on('liveRoomClose', (data) => {
   console.log('音视频通知:房间解散了')
 })
}

常见问题

小程序用户关闭麦克风再开启麦克风,其他小程序用户无法收到麦克风开启事件

问题现象

房间中的三个用户 A、B、C 都是集成小程序 SDK 的用户。用户 A 关闭麦克风后再开启麦克风,用户 B 和 C 无法收到麦克风开启的事件,看到用户 A 仍然是麦克风关闭状态,并且无法听到用户 A 的声音。

问题原因

在取消发布音频流(unpublish)后,仍会继续发送静音包,网关持续接收这些包。当小程序重新发布音频流(publish)时,网关不会触发下行的 stream-added` 通知,导致了这个问题。

问题解决

live-pusher 组件使用推流的 URL 作为 key 值控制是否渲染,关闭麦克风的时候,调用 SDK 的接口停止发布音频流。具体实现思路如下:

JavaScriptthis.client.unpublish('audio').then(url => {

    //步骤一:将推流的 URL 设置为 null,这样 live-pusher 组件就会停止渲染。

   //步骤二:网易云信只关闭了麦克风,而不关闭摄像头,所以需要立即将 URL 设置回去,以重新渲染 live-pusher 组件。同时,需要记得将 live-pusher 组件中的 muted 设置为 true,这样重新渲染的 live-pusher 组件就不会推送音频流。

})
XML<live-pusher
  wx:if="{{pusher.url}}"
  class="pusher"
  url="{{pusher.ur1}}"
  mode="RTC"
  enable-camera="{{pusher.enableCamera}}"
  muted="{{!pusher.enableMic}}"
  min-bitrate="{{pusher.minBitrate}}"
  max-bitrate="{{pusher.maxBitrate}}"
  orientation="{{pusher.videoOrientation}}"
  aspect="{{pusher.videoAspect}}"
  device-position="{{pusher.frontCamera}}"
  background-mute="{{pusher.enableBackgroundMute}}"
  audio-quality="{{pusher.audioQuality}}"
  waiting-image="{{pusher.waitingImage}}"
  bindstatechange="_pusherStateChangeHandler"
  bindnetstatus="_pusherNetStatusHandler"
  binderror="_pusherErrorHandler"
  autopush
></live-pusher>
<!-- 本地 uid 显示 -->

为什么调试 Demo 时显示无法使用摄像头和麦克风?

在调试 Demo 时,显示 无法使用摄像头和麦克风,如下图所示:

example

分析与解决:

  • 方法一:如果您在控制台上调试,则有可能报以上错误,此时请更换成真机调试即可。 example
  • 方法二:如果您没有为 Demo 程序开启摄像头麦克风权限,则有可能报以上错误。开启权限的方法如下:
    • 方式一:先删除 Demo 小程序,再重新进入,根据相关权限引导弹窗开启权限。
    • 方式二:在 Demo 小程序的右上角,单击 ...,打开 设置 按钮,从而打开麦克风和摄像头权限。
  • 方法三:请前往微信小程序官方后台,为程序开启录制和播放权限。
此文档是否对你有帮助?
有帮助
去反馈
  • GitHub 项目
  • Demo 体验
  • 前提条件
  • 实现音视频通话
  • 1. 初始化客户端对象
  • 2. 加入房间
  • 3. 发布本地流
  • 4. 订阅远端流
  • 5. 事件通知
  • 6. 监听质量数据
  • 7. 离开房间
  • 运行项目
  • Sample code
  • 常见问题
  • 小程序用户关闭麦克风再开启麦克风,其他小程序用户无法收到麦克风开启事件
  • 为什么调试 Demo 时显示无法使用摄像头和麦克风?