实现互动直播

更新时间: 2024/08/05 15:02:55

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

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

App 体验

网易云信为您提供微信小程序的多人通话 Demo 。您可以使用 Android 或 iOS 手机上打开微信 App,扫描识别下面的二维码,快速体验网易云信的微信小程序音视频通话业务场景。

G2-MiniAppDemo01

前提条件

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

实现音视频通话

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. 推流任务管理

在成功加入房间后,需要设置推流任务将通话房间内的多媒体数据推至CDN。在典型的业务场景中,由主播设置推流任务。推流任务也可以通过 服务端API 进行管理,请根据您的业务需求选择合适的方式。

注意

  • 推流任务与通话绑定,当通话结束后,推流任务也会自动随之销毁。

增加推流任务

音视频房间中默认没有推流任务,您需要在启动直播前通过 addTasks方法增加推流任务。

示例代码(增加推流任务)
js  // 示例
  // 互动直播的推流任务,可以设置多个推流任务
    let rtmpTasks = []
    // taskID 可选字母、数字,下划线,不超过64位
    let taskId = 'taskId_1' 
    // 设置推互动直播推流地址,一个推流任务对应一个推流房间
    let streamUrl = 'rtmp://xxxxxx-1' 
    // 设置是否进行互动直播录制,请注意与音视频通话录制区分。
    let record = true

    let task1 = {
      taskId,
      streamUrl,
      record,
      // 整体布局参数
      layout: {
        canvas: { 
          //整体布局宽度
          width: 1280, 
          //整体布局高度
          height: 720, 
          //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 16777215)
          color: 16777215 
        },
        users: [{
          uid: 100, //用户id
          x: 0, // user1 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user1的视频)
          y: 0, // user1 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user1的视频)
          width: 640, // user1 的视频布局宽度(前提是推流发布user1的视频)
          height: 360, //user1 的视频布局高度(前提是推流发布user1的视频)
          adaption: 1, //自适应,值默认为1,取值:0或者1
          zOrder: 1, //图层优先级
          pushAudio: true, // 推流是否发布user1 的音频
          pushVideo: true // 推流是否发布user1的视频
        },
        {
          uid: 200, //用户id
          x: 0, // user2 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user2的视频)
          y: 0, // user2 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user2的视频)
          width: 640, // user2 的视频布局宽度(前提是推流发布user2的视频)
          height: 360, //user2 的视频布局高度(前提是推流发布user2的视频)
          adaption: 1, //自适应,值默认为1
          zOrder: 2, //图层优先级 
          pushAudio: true, // 推流是否发布user2 的音频
          pushVideo: true // 推流是否发布user2 的视频
        }],
        images: [{
          url: "xxxxxx", //设置背景图片
          x: 0, // 背景图片x偏移,相对整体布局的左上角
          y: 0, // 背景图片y偏移,相对整体布局的左上角
          width: 480, // 背景图片宽度
          height: 360,  //背景图片高度
          adaption: 1 //自适应,值默认为1
        }]
      },
      //音视频属性的一些设置,可以不配置该内容(即task1不包括config字段)
      config: {
        audioParam: { 可以不配置该内容(即config不包括audioParam字段)
          bitRate: 64, //自定义音频比特率。取值范围为 10~192。语音场景建议64以上,音乐场景建议128。
          channels: 2, //音频推流声道数。可以设置为1(mono)或者2(stereo)。默认为2。
          codecProfile: 0, //音频编码规格。可以设置为以下值。0: 表示基本音频编码规格(默认), 1: 表示高效音频编码规格
          sampleRate:48000, //音频推流采样率。可以设置为以下值: 32000, 44100, 48000(默认)
          singleVideoNoTrans: false //单视频直推不转码。开启后推流服务器会透传用户的视频编码,不再对视频做转码。
        },
      }
      //扩展的推流任务属性, 可以不配置该内容(即task1不包括extraInfo字段)
      extraInfo: ''
    }
    rtmpTasks.push(task1)

    // 添加推流任务
    client.addTasks({rtmpTasks}).then(()=>{
      console.log('添加推流任务接口成功')
    }).catch(error=>{
      console.warn('添加推流任务接口失败: ', error)
    }) 

更新推流任务

当音视频通话房间内有人员进出或其他情况时,可以使用 updateTasks 方法更新推流任务。更新推流任务时,会覆盖之前对于这条推流任务的所有配置。

示例代码(更新推流任务)
js  // 示例
  let rtmpTasks = []
    // taskID 可选字母、数字,下划线,不超过64位(原推流任务taskId)
    let updatTaskId = taskId
    // 更新推互动直播推流地址
    let streamUrl = 'rtmp://xxxxxx-1' 
    // 更新互动直播录制
    let record = true
    //选填,指定大画面uid
    let hostUid = 100

    let task1 = {
      taskId,
      streamUrl,
      record,
      hostUid,
      // 整体布局参数
      layout: {
        canvas: { 
          //整体布局宽度
          width: 1280, 
          //整体布局高度
          height: 720, 
          //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 16777215)
          color: 16777215 
        },
        users: [{
          uid: 100, //用户id
          x: 0, // user1 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user1的视频)
          y: 0, // user1 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user1的视频)
          width: 640, // user1 的视频布局宽度(前提是推流发布user1的视频)
          height: 360, //user1 的视频布局高度(前提是推流发布user1的视频)
          adaption: 1, //自适应,值默认为1
          pushAudio: true, // 推流是否发布user1 的音频
          pushVideo: true // 推流是否发布user1的视频
        },
        {
          uid: 200, //用户id
          x: 0, // user2 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user2的视频)
          y: 0, // user2 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user2的视频)
          width: 640, // user2 的视频布局宽度(前提是推流发布user2的视频)
          height: 360, //user2 的视频布局高度(前提是推流发布user2的视频)
          adaption: 1, //自适应,值默认为1
          pushAudio: true, // 推流是否发布user2 的音频
          pushVideo: true // 推流是否发布user2 的视频
        }],
        images: [{
          url: "xxxxxx", //设置背景图片
          x: 0, // 背景图片x偏移,相对整体布局的左上角
          y: 0, // 背景图片y偏移,相对整体布局的左上角
          width: 480, // 背景图片宽度
          height: 360,  //背景图片高度
          adaption: 1 //自适应,值默认为1
        }]
      },
      //音视频属性的一些设置,可以不配置该内容(即task1不包括config字段)
      config: {
        audioParam: { 可以不配置该内容(即config不包括audioParam字段)
          bitRate: 64, //自定义音频比特率。取值范围为 10~192。语音场景建议64以上,音乐场景建议128。
          channels: 2, //音频推流声道数。可以设置为1(mono)或者2(stereo)。默认为2。
          codecProfile: 0, //音频编码规格。可以设置为以下值。0: 表示基本音频编码规格(默认), 1: 表示高效音频编码规格
          sampleRate:48000, //音频推流采样率。可以设置为以下值: 32000, 44100, 48000(默认)
          singleVideoNoTrans: false //单视频直推不转码。开启后推流服务器会透传用户的视频编码,不再对视频做转码。
        },
      }
      //扩展的推流任务属性, 可以不配置该内容(即task1不包括extraInfo字段)
      extraInfo: ''
    }
    rtmpTasks.push(task1)

    // 添加推流任务
    client.updateTasks({rtmpTasks}).then(()=>{
      console.log('更新推流任务接口成功')
    }).catch(error=>{
      console.warn('更新推流任务接口失败: ', error)
    }) 

删除推流任务

当本场互动直播准备结束时,可以通过 deleteTasks 方法主动删除推流任务。

示例代码(删除推流任务)
js  // 示例
  // 传入需要删除的推流任务的taskId
    let  taskIds = []
    taskIds.push('taskId_1')

    client.deleteTasks({
      taskIds
    }).then(()=>{
      console.log('删除推流任务接口调用成功')
    }).catch(error=>{
      console.warn('删除推流任务接口调用失败: ', error)
    })

互动直播推流状态

主播/连麦者参与互动直播的过程中,可以通过 rtmp-tasks-status 来监听推流状态。

示例代码(监听推流状态)
js   //通知应用程序, 互动直播的推流状态
  client.on('rtmp-tasks-status', ({taskId, code, msg, streamUrl}) => {
    console.log('音视频通知:设置的推流任务的状态发生了变更')
    //taskId: 设置的推流任务的id
    //streamUrl: 设置的推流任务对应的推流地址
    if (code == 505) {
      console.warn('该推流任务正在推流中,状态正常')
    } else if (code == 506) {
      console.warn('该推流任务推流失败了')
    } else if (code == 511) {
      console.warn('该推流任务推流结束了')
    }    
  })     

4. 发布本地流

成功加入房间后,就能调用 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);
})

5. 订阅远端流

当远端流发布到房间时,会触发 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} => {
  let uid = e.uid;
  console.log(`[stream-removed 通知] ${uid} 停止发布自己的 ${mediaType}`)
  //业务层做停止推流动作,清除响应的live-player组件
});

6. 事件通知

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('音视频通知:房间解散了')  
    }) 
    

7. 监听质量数据

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

live-pusher 组件监听事件

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

其中:

  • bindstatechange:状态变化事件
  • bindnetstatus:网络状态通知
  • binderror:渲染错误事件
<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 进行问题排查、质量优化。也可以自己监听这些信息,进行进一步的业务处理。

示例代码:

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

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

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

live-player 组件监听事件

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

其中:

  • bindstatechange:播放状态变化事件
  • bindnetstatus:网络状态通知
<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 进行问题排查、质量优化。也可以自己监听这些信息,进行进一步的业务处理。

示例代码:

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

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

8. 离开房间

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

运行项目

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

Sample code

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

import 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('音视频通知:房间解散了')  
 }) 
}
此文档是否对你有帮助?
有帮助
去反馈
  • App 体验
  • 前提条件
  • 实现音视频通话
  • 1. 初始化客户端对象
  • 2. 加入房间
  • 3. 推流任务管理
  • 增加推流任务
  • 更新推流任务
  • 删除推流任务
  • 互动直播推流状态
  • 4. 发布本地流
  • 5. 订阅远端流
  • 6. 事件通知
  • 7. 监听质量数据
  • live-pusher 组件监听事件
  • live-player 组件监听事件
  • 8. 离开房间
  • 运行项目
  • Sample code