快速入门

本文为您介绍如何使用 NERTC SDK 快速实现互动直播功能。

前提条件

在开始运行工程之前,需要准备好如下材料:

集成SDK

集成前,请先前往SDK下载页面查看当前最新版本。将下载得到的 NIM_Web_WebRTC2_vx.x.x.js 文件拷贝到项目文件所在的目录下。通过如下方式引入:

import方式引入

    import WebRTC2 from '../../NIM_Web_WebRTC2_vx.x.x.js'

script标签引入

    <script src="./NIM_Web_WebRTC2_vx.x.x.js"></script>

至此,SDK 已经导入完成。

实现互动直播

本节主要介绍如何使用 SDK 互动直播通话。

SDK中有两个核心对象:

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

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

初始化

为方便起见,我们为下面要用到的示例代码定义了两个变量。此步骤不是必须的,你可以根据你的项目有其他的实现。

// rtc object
let rtc = {
  client: null,
  localStream: null,
};

以下为创建Client对象实例的方法:

//创建client实例
rtc.client = WebRTC2.createClient({
    appkey: 'xxx', //您的 appkey
    debug: true, //是否开启调试日志
});

设置直播模式

在互动直播的场景中,我们建议在加入频道前,设置频道模式为直播模式。当前默认为通信模式。

rtc.client.setChannelProfile(
        mode:'live'  //房间属性,"rtc": 通信场景,"live": 直播场景
).then((obj) => {
    console.info('设置互动直播模式成功...')
})

加入频道并设置推流开关

加入频道前,请确保已完成初始化相关事项。若您的业务中涉及呼叫邀请等机制,可以使用信令

//加入频道
rtc.client.join({
    channelName: '频道名称',
    uid: uid, 
    token: '',
    liveEnable:true
}).then((obj) => {
    console.info('加入频道成功...')
    //初始化本地流,并且发布
    initLocalStream() //后面介绍说明
})

参数说明

推流任务

在成功加入房间后,需要设置推流任务。典型的业务场景里是由主播进行设置。注意:此处的主播业务层面的角色,NERTC SDK体系内不区分主播与连麦者。

推流任务也可通过 服务端API 进行管理,请根据您的业务需求选择对应的方式。

添加推流任务

在成功加入房间后,即可以动态的添加推流任务,示例代码:

//互动直播的推流任务,可以设置多个推流任务
let rtmpTasks = []

//设置推流任务 - 1
let taskId = 'taskId_1' //推流任务ID,string格式。taskId为推流任务的唯一标识,用于过程中增删任务操作
let streamUrl = 'rtmp://xxxxxx-1' //直播的rtmp推流url,保证一个url 只能设置一个task 
let record = true //互动直播是否启用录制
// 整体布局参数
let task1 = {
  taskId,
  streamUrl,
  record,
  layout: {
    canvas: { //整体布局大小
      width: 1280, //整体布局宽度
      height: 720, //整体布局高度
      color: 16777215 //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 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
    }]
  }

}

rtmpTasks.push(task1)


//设置推流任务 - 2
taskId = 'taskId_2' //推流任务ID,string格式。taskId为推流任务的唯一标识,用于过程中增删任务操作
streamUrl = 'rtmp://xxxxxx-2' //直播的rtmp推流url,保证一个url 只能设置一个task 
record = true //互动直播是否启用录制
// 整体布局参数
let task2 = {
  taskId,
  streamUrl,
  record,
  layout: {
    canvas: { //整体布局大小
      width: 1280, //整体布局宽度
      height: 720, //整体布局高度
      color: 16777215 //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 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
    }]
  }
}

rtmpTasks.push(task2)

rtc.client.addTasks({rtmpTasks}).then(()=>{
  console.log('添加推流任务接口成功')
}).catch(error=>{
  console.warn('添加推流任务接口失败: ', error)
  if (error == 'INVALID_PARAMETER') {
    console.log('参数错误')
  }
})

更新推流任务

在成功加入房间后,可以动态的更新现有的推流任务,示例代码:

//可以同时更新多个推流任务
let rtmpTasks = []

//更新推流任务 - 1
let taskId = 'taskId_1' //推流任务ID
let streamUrl = 'rtmp://xxxxxx-1' //直播的rtmp推流url
let record = true //互动直播是否启用录制
// 整体布局参数
let task1 = {
  taskId,
  streamUrl,
  record,
  layout: {
    canvas: { //整体布局大小
      width: 1280, //整体布局宽度
      height: 720, //整体布局高度
      color: 16777215 //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 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
    }]
  }
}

rtmpTasks.push(task1)


//更新推流任务 - 2
taskId = 'taskId_2' //推流任务ID
streamUrl = 'rtmp://xxxxxx-2' //直播的rtmp推流url
record = true //互动直播是否启用录制
// 整体布局参数
let task2 = {
  taskId,
  streamUrl,
  record,
  layout: {
    canvas: { //整体布局大小
      width: 1280, //整体布局宽度
      height: 720, //整体布局高度
      color: 16777215 //整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 16777215
    },
    users: [{
        uid: 101, //用户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
    }]
  }

}

rtmpTasks.push(task2)

rtc.client.updateTasks({rtmpTasks}).then(()=>{
  console.log('更新推流任务接口调用成功')
}).catch(error=>{
  console.warn('更新推流任务接口调用失败: ', error)
  if (error == 'INVALID_PARAMETER') {
    console.log('参数错误')
  }
})

删除推流任务

当不再需要推流时,可以选择删掉之前配置的推流任务。

注意:推流任务不会随着成员的离开而自动结束,成员离开只是结束了自己的推流,只有在手动删除或者频道所有成员都离开时才会结束。

在成功加入房间后,可以动态的删除现有的推流任务,示例代码:

//可以同时删除多个推流任务
let  taskIds = []
taskIds.push('taskId_1')
taskIds.push('taskId_2')

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

监听推流任务状态

示例代码:

//可以监听推流任务的状态
rtc.client.on('remp-state', _data => {
    console.warn('=====互动直播状况:', _data)
    console.warn(`互动直播推流任务:${_data.task_id},的状态:${_data.state}`)
    if (_data.state == 505) {
      console.log('该推流任务正在推流中,状态正常')
    } else if (_data.state == 506) {
      console.log('该推流任务推流失败了')
    } else if (_data.state == 511) {
      console.log('该推流任务推流结束了')
    }
  })

API参考

方法 功能描述
addTasks 增加旁路推流任务
deleteTasks 删除旁路推流任务
updateTasks 更新旁路推流任务

设置本地视图

在加入频道成功之后就可以启动本地音视频流。

//初始化本地流并且发布
function initLocalStream() {
  //创建本端stream实例,销毁前无需重复创建
  rtc.localStream = WebRTC2.createStream({
    audio: true,                   // 是否从麦克风采集音频
    microphoneId: microphoneId,    // 麦克风设备 deviceId,通过 getMicrophones() 获取
    video: true,                   // 是否从摄像头采集视频
    cameraId: 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实例后,可以监听监听远端用户进出频道:

//监听用户进入频道事件
rtc.client.on('peer-online', (_event) => {
    console.log('有人加入', _event)
})
//监听用户离开频道事件
rtc.client.on('peer-leave', (_event) => {
    console.log('有人离开频道', _event)
})

监听到远端用户加入频道后,做自己的上层业务逻辑处理

监听远端音视频流发布

当频道中的其他用户有音视频流发出/关闭时,分别会走入以下回调:

//监听频道里其他人发布音视频
rtc.client.on('stream-added', evt => {
  var remoteStream = evt.stream;
  console.warn('收到别人的发布消息: ', remoteStream.streamID)
  // 发起视频订阅
})

//监听频道里其他人停止发布音视频
rtc.client.on('stream-removed', evt => {
  var remoteStream = evt.stream;
  console.warn('对方停止发布: ', remoteStream.getId())
  this.preprocessingDiv()
  remoteStream.stop()
})

订阅远端音视频流

监听到远端用户有音视频发布时,可以订阅远端用户的音视频流。

  //设置要订阅音频或者视频
  remoteStream.setSubscribeConfig({
    audio: true,
    video: true
  })
  rtc.client.subscribe(remoteStream).then(()=>{
    console.log('订阅对端成功')
  })

若订阅成功,可进一步在订阅成功通知回调中播放远端视频流:

//播放订阅的对端的音视频流
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
    })
  })
})

离开频道

当通话结束,需要离开频道,可以调用以下接口:

  //用户无需做一些清除动作,sdk会自动做清除逻辑
  rtc.client.leave()

销毁音视频实例

释放当前的 client 实例,建议在程序确定不再需要使用 client 实例时,通过该接口释放 client 实例的对象资源。实例销毁后需要重新初始化。

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