跳到主要内容

实现低延迟直播

融云开发者账户是使用融云 SDK 产品的必要条件。在开始之前,请先前往融云官网注册开发者账户。注册后,控制台将自动为你创建一个应用,默认为开发环境应用,使用国内数据中心。请获取该应用的 App Key,在本教程中使用。

首次使用融云音视频的用户,建议参考文档运行示例项目,以完成开发者账号注册、音视频服务开通等工作。

步骤 1:服务开通

您在融云创建的应用默认不会启用音视频服务。在使用融云提供的任何音视频服务前,您需要前往控制台,为应用开通音视频服务。

具体步骤请参阅开通音视频服务

提示

服务开通、关闭等设置完成后 30 分钟后生效。

步骤 2:SDK 导入

RTCLib 相关业务依赖 IMLib 作为信令通道。因此,开发音视频会议必须安装融云音视频核心能力库 RTCLib,即时通讯能力库 IMLib。

安装 IMLib

推荐使用 IMLib 5.X。您也可以使用 2.x 或 4.x 的 Adapter SDK。

# 安装 RongIMLib v5
npm install @rongcloud/engine@latest @rongcloud/imlib-next --save

安装 RTCLib

# 安装 RTCLib
npm install @rongcloud/plugin-rtc --save

上述步骤仅简要说明安装方法,详见安装 RTCLib SDK

步骤 3:初始化

您需要先初始化 IMLib,再初始化 RTCLib。

初始化 IMLib

RTCLib 是基于 IMLib 的插件机制所实现的插件。因此需要先初始化 IMLib。推荐使用 IMLib 5.X。

以下示例基于 IMLib 5.X。使用 Typescript 进行编码,便于开发者更好的理解相关值的类型信息。

import * as RongIMLib from '@rongcloud/imlib-next'
import { installer, RCRTCCode } from '@rongcloud/plugin-rtc'
// 初始化 IM
RongIMLib.init({
appkey: '<Your-Appkey>',
});

/**
* 监听消息通知
*/
const Events = RongIMLib.Events;
RongIMLib.addEventListener(Events.MESSAGES, (event) => {
console.log('received messages', event.messages);
});

/**
* 监听 IM 连接状态变化
*/
RongIMLib.addEventListener(Events.CONNECTING, () => {
console.log('onConnecting');
});
RongIMLib.addEventListener(Events.CONNECTED, () => {
console.log('onConnected');
});
RongIMLib.addEventListener(Events.DISCONNECT, (status) => {
console.log('连接中断,需要业务层进行重连处理 ->', status)
})

初始化 RTCLib

如果使用 IMLib 5.X,需要调用 installPlugin 方法初始化 RCRTCClientoptions 参数的详细说明参见 IRCRTCInitOptions

以下示例基于 IMLib 5.X。使用 Typescript 进行编码,便于开发者更好的理解相关值的类型信息。

// 初始化 RCRTCClient,初始化过程需要在建立连接之前
const rtcClient = RongIMLib.installPlugin(installer, {

/**
* 自定义 MediaServer Url,公有云用户无需关注
* @description
* 1. 仅当 `location.hostname` 为 `localhost` 时,`http` 协议地址有效,否则必须使用 `https` 协议地址
* 2. 当该值有效时,将不再从 IMLib 导航数据中获取 mediaServer 地址
*/
mediaServer?: string,
/**
* 输出日志等级,生产环境默认使用 WARN,开发环境默认使用 DEBUG
* @description
* * 4 - DEBUG
* * 3 - INFO
* * 2 - WARN
* * 1 - ERROR
*/
logLevel?: LogLevel
/**
* 与 MediaServer 的 http 请求超时时间,单位为毫秒,默认值为 `5000`,有效值 `5000-30000`。
* 优先级:用户配置 > 导航配置 > 默认时间。
*/
timeout?: number,
/**
* 房间 Ping 间隔时长,默认 `10000` ms,有效值 `3000`-`10000`
*/
pingGap?: number,
/**
* 内置 CDN 观看地址直播拉流协议,默认为 RCInnerCDNPullKind.FLV
*/
pullInnerCDNProtocol?: RCInnerCDNPullKind
/**
* 内置 CDN 观看地址是否使用 https,默认为 RCInnerCDNPullIsHttps.HTTPS
*/
pullInnerCDNUseHttps?: RCInnerCDNPullIsHttps
})

上述步骤仅简要说明初始化方法。详细说明请参阅初始化

步骤 4:连接融云

音视频用户之间的信令传输依赖于融云的即时通信(IM)服务。应用客户端成功连接到融云服务器后,才能使用融云即时通讯 SDK 进行信令传输。

连接时融云服务端必须传入 Token 参数。Token 是与用户 ID 对应的身份验证令牌,是应用客户端用户在融云的唯一身份标识。融云服务端在收到客户端发起的连接请求后,会根据连接请求里携带的用户身份验证令牌(Token 参数),判断是否允许用户连接。

在实际业务运行过程中,应用客户端需要通过应用的服务端向融云服务端申请取得 Token,具体方法可参考 Server API 获取 Token

如果仅希望快速体验和测试,您可以直接从融云控制台获取一个用户 Token。具体操作请参见运行示例项目

以下示例使用 Typescript 进行编码,便于开发者更好的理解相关值的类型信息。

import * as RongIMLib from '@rongcloud/imlib-next'
import { installer, RCRTCCode } from '@rongcloud/plugin-rtc'

// 建立 IM 连接
RongIMLib.connect('<Your-Token>').then((user) => {
console.log('connect success', user.data.userId);
})
.catch((error) => {
console.log(`连接失败: ${error}`);
});

主播端

步骤 5.1: 加入房间

RTCLib 初始化后,可获取到 RTC 客户端实例。调用 joinLivingRoom 方法可加入房间。

以下示例中 rtcClient 表示 RTC 客户端实例。加入房间成功后,会返回主播角色用户的 RCLivingRoom 房间实例、房间内其他用户 ID,以及房间内已发布的资源。

// 获取 RCLivingType 枚举定义
import { RCLivingType } from '@rongcloud/plugin-rtc'

/**
* 主播加入直播房间或观众上麦场景调用,观众上麦之前需先取消已订阅的直播间资源
* 从 5.0.7 开始增加返回 `tracks` 与 `userIds`
* userIds - 当前已加入房间的主播人员列表
* tracks - 当前已发布至房间内的其他主播资源
* 5.3.2 新增返回 PKRoomIds,为房间内已连麦的副房间 roomId 列表
* @param roomId 房间 Id
* @param livingType 直播类型
* * 当 `livingType` 值为 `RCLivingType.AUDIO` 时表示开始音频直播
* * 当 `livingType` 值为 `RCLivingType.VIDEO` 时表示开始音视频直播
*/
const { code, room, userIds, tracks: remoteTracks, PKRoomIds: string[] } = await rtcClient.joinLivingRoom('roomId', RCLivingType.VIDEO)å

// 若加入失败,则 room、userIds、tracks 值为 undefined
if (code !== RCRTCCode.SUCCESS) {
console.log('join room failed:', code)
return
}

// 注册房间事件监听器,重复注册时,仅最后一次注册有效
room.registerRoomEventListener({
/**
* 当本端被剔出房间
* @description 被踢出房间可能是由于服务端超出一定时间未能收到 rtcPing 消息,所以认为己方离线。
* 另一种可能是己方 rtcPing 失败次数超出上限,故而主动断线
* @param byServer
* 当值为 false 时,说明本端 rtcPing 超时
* 当值为 true 时,说明本端收到被踢出房间通知
* @param state 被踢出房间的原因
*/
onKickOff? (byServer: boolean, state?: RCKickReason): void
},
/**
* 接收到房间信令时回调,用户可通过房间实例的 `sendMessage(name, content)` 接口发送信令
* @param name 信令名
* @param content 信令内容
* @param senderUserId 发送者 Id
* @param messageUId 消息唯一标识
*/
onMessageReceive (name: string, content: any, senderUserId: string, messageUId: string) {
},
/**
* 监听房间属性变更通知
* @param name
* @param content
*/
onRoomAttributeChange (name: string, content: string) {
},
/**
* 房间用户禁用/启用音频
* @param audioTrack RCRemoteAudioTrack 类实例
*/
onAudioMuteChange (audioTrack: RCRemoteAudioTrack) {
},
/**
* 房间用户禁用/启用视频
* @param videoTrack RCRemoteVideoTrack 类实例对象
*/
onVideoMuteChange (videoTrack: RCRemoteVideoTrack) {
},
/**
* 房间内用户发布资源
* @param tracks 新发布的音轨与视轨数据列表,包含新发布的 RCRemoteAudioTrack 与 RCRemoteVideoTrack 实例
*/
async onTrackPublish (tracks: RCRemoteTrack[]) {
// 按业务需求选择需要订阅资源,通过 room.subscribe 接口进行订阅
const { code } = await room.subscribe(tracks)
if (code !== RCRTCCode.SUCCESS) {
console.log('资源订阅失败 ->', code)
}
},
/**
* 房间用户取消发布资源
* @param tracks 被取消发布的音轨与视轨数据列表
* @description 当资源被取消发布时,SDK 内部会取消对相关资源的订阅,业务层仅需处理 UI 业务
*/
onTrackUnpublish (tracks: RCRemoteTrack[]) {
},
/**
* 订阅的音视频流通道已建立, track 已可以进行播放
* @param track RCRemoteTrack 类实例
*/
onTrackReady (track: RCRemoteTrack) {
if (track.isAudioTrack()) {
// 音轨不需要传递播放控件
track.play()
} else {
// 视轨需要一个 video 标签才可进行播放
const element = document.createElement('video')
document.body.appendChild(element)
track.play(element)
}
},
/**
* 人员加入
* @param userIds 加入的人员 id 列表
*/
onUserJoin (userIds: string[]) {
},
/**
* 人员退出
* @param userIds
*/
onUserLeave (userIds: string[]) {
},
/**
* 房间内主播和观众切换身份(@rongcloud/plugin-rtc@5.2.0 新增)
* @description @rongcloud/plugin-rtc@5.2.0 及其之后,
* 如业务层未传入 onSwitchRole 回调,“房间内观众升级为主播”通过 onUserJoin 通知,“主播降级为房间内的观众”通过 onUserLeave 通知;
* 业务层传入 onSwitchRole 时,“房间内主播和观众切换身份”通过 onSwitchRole 通知,onUserJoin 和 onUserLeave 仅通知手动调用“加入或退出房间”的人员
* @param userId 用户 ID
* @param role 用户角色
* role 值为 RCRTCLiveRole.ANCHOR 时,代表房间内观众升级为主播
* role 值为 RCRTCLiveRole.AUDIENCE 时,代表主播降级为房间内的观众
*/
onSwitchRole (userId: string, role: RCRTCLiveRole) {
}
})

步骤 5.2: 捕获本地音视频资源

使用初始化时获取的 RCRTCClient 实例的 createMicrophoneAndCameraTracks 方法可同时捕获音视频流。

/**
* @description tracks 是一个数组,当 `code !== RCRTCCode.SUCCESS` 时,tracks 长度为 0
* @param tag 资源标识,不传时默认为 RongCloudRTC,代表浏览器摄像头、麦克风资源,
* 也可传入其他包含 A-Z、a-z、0-9、+、=、- 的字符串,
* @param options 音视频配置项,可参考上述 1、2 中的介绍
*/
const { code, tracks } = await rtcClient.createMicrophoneAndCameraTracks(tag: string = 'RongCloudRTC', options?: { audio?: IMicphoneAudioProfile, video?: ICameraVideoProfile })

if (code === RCRTCCode.SUCCESS) {
// tracks 包含一个 RCMicphoneAudioTrack 实例和一个 RCCameraVideoTrack 实例
const [ audioTrack, videoTrack ] = tracks
}

使用 play 方法在本端播放采集的数据。通常情况下,尽量不要在本端播放本端采集的音频流,否则可能会引起回声问题。

// 通过 videoTrack.play 方法将 <video> 标签传递给 videoTrack 实例
videoTrack.play(videoNode)

// 播放音频时无需传参,尽量不要在本端播放本端采集的音频流,因为可能会引起回声问题
audioTrack.play()

// 播放音频设置音量,volume 为 0-100 的数字
audioTrack.play(null, {volume})

// 指定播放音频的输出设备,输出音频设备列表可通过 device.getSpeakers() 获取
// device 可从 @rongcloud/plugin-rtc 模块导出
audioTrack.play(null, {audioDeviceId})

步骤 5.3: 发布资源

使用 RCLivingRoom 房间实例的 publish 方法发布一个或多个音视频轨道 RCLocalTrack 数据。

const { code } = await room.publish([audioTrack, videoTrack])

// 若资源发布失败
if (code !== RCRTCCode.SUCCESS) {
console.log('资源发布失败:', code)
}

步骤 5.4: 资源订阅

使用 RCLivingRoom 房间实例的 subscribe 方法订阅音视轨资源。

const { code } = await room.subscribe([
// 音频不支持大小流
audioTrack,
{
track: videoTrack,
subTiny: true // 订阅小流
}
])

步骤 5.5: 退出房间

使用 RCRTCClient 实例的 leaveRoom 方法,传入 RCLivingRoom 房间实例退出该房间。

// room 为加入房间方法返回的实例对象
const { code } = await rtcClient.leaveRoom(room)

观众端

步骤 6.1: 加入房间

RTCLib 初始化后,可获取到 RTC 客户端实例。调用 joinLivingRoomAsAudience 方法可加入房间。

以下示例中 rtcClient 表示 RTC 客户端实例。加入房间成功后,会返回主播角色用户的 RCAudienceLivingRoom 房间实例、房间内其他用户 ID,以及房间内已发布的资源。

// 获取 RCLivingType 枚举定义
import { RCLivingType } from '@rongcloud/plugin-rtc'

/*
* 观众加入直播房间调用
* @param roomId 房间 Id
* @param livingType 直播类型
* * 当 `livingType` 值为 `RCLivingType.AUDIO` 是表示音频直播
* * 当 `livingType` 值为 `RCLivingType.AUDIO_VIDEO` 是表示音视频直播
*/
// 从 5.2.3 开始,加入房间时可返回 RTCTracks、MCUTracks、CDNUris、userIds
const { room: audienceRoom, RTCTracks, MCUTracks, CDNUris, userIds, code } = await rtcClient.joinLivingRoomAsAudience('roomId', RCLivingType.AUDIO_VIDEO)
// 若加入失败,则 room 值为 undefined
if (code !== RCRTCCode.SUCCESS) {
console.log('join room as audience failed:', code)
}

// audienceRoom 为观众加入房间返回的 room
// 注册房间事件监听器,重复注册时,仅最后一次有效
audienceRoom.registerRoomEventListener({
/**
* 主播加入
* @param userIds 加入的主播 id 列表
*/
onAnchorJoin (userIds: string[]) {
},
/**
* 主播离开
* @param userIds 离开的主播 id 列表
*/
onAnchorLeave (userIds: string[]) {
},
/*
* 房间内直播合流资源发布
* @param track RCRemoteTrack 类实例
* * 房间类型以第一个加入房间用户设置的直播类型为准
* * 房间直播类型为 RCLivingType.AUDIO_VIDEO ,tracks 包含 RCRemoteAudioTrack 与 RCRemoteVideoTrack 实例
* * 直播类型为 RCLivingType.AUDIO , tracks 仅包含 RCRemoteAudioTrack 实例
* * 触发时机: 主播发布资源后
*/
onTrackPublish (tracks: RCRemoteTrack[]) {
},
/*
* 房间内直播合流资源取消发布
* @param track RCRemoteTrack 类实例
* * 触发时机: 全部主播退出房间(因资源为多个主播发布的合流资源,单个主播取消发布不会触发)
*/
onTrackUnpublish (tracks: RCRemoteTrack[]) {
},
/*
* 房间内主播资源发布
* @param track RCRemoteTrack 类实例
* * 触发时机: 主播发布资源后
*/
onAnchorTrackPublish (tracks: RCRemoteTrack[]){
},
/*
* 房间内主播资源取消发布
* @param track RCRemoteTrack 类实例
* * 触发时机: 主播取消发布资源后
*/
onAnchorTrackUnpublish (tracks: RCRemoteTrack[]){
},
/**
* 订阅的音视频流通道已建立, track 已可以进行播放
* @param track RCRemoteTrack 类实例
*/
onTrackReady (track: RCRemoteTrack) {
// 订阅的音视频轨道已连接,可以根据业务需求选择性播放
if (track.isAudioTrack()) {
// 音频播放无需传递组件
track.play()
} else {
// 此处的 videoNode 为 <video> 标签元素实例
track.play(videoNode)
}
}
})

加入直播间后,直播间内可能已经存在主播发布的音视轨数据,可以通过 subscribe 接口拉取这些声音或图像资源。

const { code } = await room.subscribe(remoteTracks)

if (code !== RCRTCCode.SUCCESS) {
console.log(`资源订阅失败 -> code: ${code}`)
}

// 当资源订阅成功后,等待 onTrackReady 事件回调即可进行播放

步骤 6.2: 订阅资源

使用 RCAudienceLivingRoom 房间实例的 subscribe 方法订阅一个或多个音视频轨道 RCLocalTrack 数据。

const { code } = await audienceRoom.subscribe(tracks)
if (code !== RCRTCCode.SUCCESS) {
console.log('资源订阅失败 ->', code)
}

步骤 6.3: 退出房间

使用 RCRTCClient 实例的 leaveLivingRoomAsAudience 方法,传入 RCAudienceLivingRoom 房间实例,退出该直播房间。

/*
* @param audienceRoom 观众加入直播房间返回的 room
*/
const { code } = await rtcClient.leaveLivingRoomAsAudience(audienceRoom)
if (code !== RCRTCCode.SUCCESS) {
console.log('join room as audience failed:', code)
}