如视 Live SDK

Live.js

npm version

Live如视开放平台线上带看 的核心客户端 Web SDK,搭配 Live Container SDKs(iOS/Android/微信小程序)和 Live API 可以快速实现实时同屏、实时语音、IM 等功能。

  • 帧数据(Keyframe): 指用户界面需同屏交互信息的状态数据的集合。

通常以键值对的形式表现——键为界面模块名字,值为该模块的状态信息。
比如帧数据 {"popupPanel": { hidden: true }} 表示界面交互中有个弹窗面板,我们命名为popupPanel,它有个hidden状态,true表示其正处于隐藏状态。

  • 快照(Snapshot): 指某个时刻所有 帧数据 的集合。

您可以理解 帧数据快照 的子集,通过某时刻的"快照"信息能够还原该时刻的用户界面所有状态。

  • RTC: Real-Time Communication, 在本文中则特指渲染 HTML 页面的 WebView/Browser 等容器的音视频实时通信能力。

需注意的是 Live 并不包含 RTC 功能的实现,需借助客户端原生能力的支持。更多细节详见后文 RTC 协议 部分。

可以通过 Node 包管理工具 npmyarn 安装Live

# npm
npm i @realsee/live
# yarn
yarn add @realsee/live

简单使用样例(先不考虑实时语音能力):

import { createLive } from '@realsee/live'

// 定义帧数据类型(可选,但推荐使用 TypeScript)
interface MyKeyframes {
camera: { position: [number, number, number] }
annotation: { text: string; position: [number, number] }
}

const live = createLive<MyKeyframes>({
url: 'wss://ws-access.realsee.com/***/?=xxx' /* 连接 WebSocket 服务的 URL */,
getTicket: async () => {
/* 获取带看"门票"的回调函数 */
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
})

// 连接带看
await live.connect()

在创建 live 实例时配置了两个参数:

  • url:连接 WebSocket 服务的 URL(可选),如果在创建 live 实例阶段并不知道 URL,您可以在合适的时机通过 live.connect({ url }) 方法进行 WebSocket 连接。
  • getTicket():是一个异步回调函数(可选),其返回值是连接 WebSocket 服务的 "门票",关于如何获取 ticket 请查阅 如视开放平台带看文档。也可以在 connect 时提供。

提示:如果在创建实例时同时提供了 urlgetTicket,Live 会自动尝试连接,无需手动调用 connect() 方法。

关于 createLive() 函数的更多参数配置请参考 LiveOptions 文档。

获取实例 live 之后,可以做很多有趣的事情。但在讲述详细用途之前,先简单介绍下 Live 中的 消息系统

Live 核心职责是借助 WebSocket 长链双工通信能力实现多端用户的同屏连线。用户间的通信都是基于 WebSocket 消息。

Live 将消息分为帧数据广播房间信息三类。针对每一类信息提供相应的发送、接收 API,更多 API 细节请参考 Live API 文档

帧数据 是进行同屏的信息载体,由用户自行定义当前场景中哪些信息用以同屏,将这些信息组织成 {key: value} 的形式即可。

广播消息 用以房间成员之间以事件的形式指定某个用户向其发送自定义广播消息。如:主持人向观众发送问候消息。

房间信息 为内置消息体,由 WebSocket 服务主动推送,包括房间状态、当前用户及参与用户列表等。部分信息也可通过 Live 提供的更新方式进行修改。

当前 UI 交互状态的同步:

Five 同屏为例

// 发送本地 帧数据 (Five State)
live.sendKeyframe('five', { panoIndex: 13 })

// 接收远程 帧数据 (Five State)
live.keyframes.on('five', (newState, prevState, frontRequestId) => {
// 可以拿最新的 `newState` 更新本地 UI 状态
})

您也可以通过 live.snapshot 属性来获取当前所有序列帧数据的"快照"。

Live 有会话房间的概念,当您成功连接 WebSocket 服务之后等同于您作为一个用户加入了一个房间。此房间用户除了您之外还会有其他用户,您可以通过如下方式获取或更新用户信息:

// 属性:获取本地用户信息
live.selfInfo

// 属性:获取当前房间内所有用户列表
live.userList

// 属性:获取房间信息
live.roomInfo

// 方法:更新本地用户信息
live.setSelfInfo({})

// 事件:本地用户信息发生变更
live.on('selfInfoUpdate', (userInfo, frontRequestId) => {})

// 事件:房间用户列表信息发生变更
live.on('userListUpdate', (userList, frontRequestId) => {})

除了帧数据和房间信息之外,还可以用事件的形式向房间内的其他用户广播消息:

live.broadcast(
data /* 广播的数据 */,
['user_id_1', 'user_id_2'] /* 收到此消息的用户ID列表 */,
0 /* 超时时间(毫秒),0 表示不超时 */
)

此时,房间内用户 Id 为 user_id_1user_id_2 的用户会收到此广播消息(其他用户无感知):

live.on('broadcast', (data /* 广播的数据 */, frontRequestId) => {})

更多的例子请查看 Live API 文档 。

Live 提供 带看事件RTC 事件 两类异步事件的监听。

所有事件都可以通过 live.on('/*事件名*/', /*事件回调*/) 的方式监听。

比如监听连接状态变化:

live.on('stateChange', (state, prevState) => {
console.log(`状态从 ${prevState} 变为 ${state}`)
})

Live 前后端服务交互是基于 WebSocket 长链接的。一次带看成功进入、退出的标识由连接状态 OPENCLOSED 体现。因此,所有 VRTC 相关的事件处理逻辑应保证在 OPEN 状态之后进行。

连接状态枚举表:

状态名称 状态描述
NOTINITIALIZED 未被初始化
CONNECTING 正在连接中
OPEN 连接成功
CLOSING 关闭中
CLOSED 关闭成功
  • stateChange(state: LiveState, prevState?: LiveState): void - 带看连接状态变化事件

  • builtinEvent(builtinMsg: BuiltinMsg): void - 接收服务端推送的内置事件消息(包括用户变化和房间变化)

  • userChange(builtinMsg: BuiltinMsg): void - 用户信息改变事件(是 builtinEvent 的子集,event_type 为 UserChange)

  • roomChange(builtinMsg: BuiltinMsg): void - 房间信息改变事件(是 builtinEvent 的子集,event_type 为 RoomChange)

  • broadcast(evtMsg: Record<string, any>, frontRequestId: string): void - 接收其他用户的广播消息

  • keyframes(keyframes: Partial<Snapshot>, frontRequestId: string): void - 接收其他用户的帧数据

  • readyKeyframeSync(lastKeyframe: Partial<Snapshot>): void - 表明帧同步行为已就绪(已废弃,请使用 keyframes 事件)

  • selfInfoUpdate(userInfo: UserInfo, frontRequestId: string): void - 自身的信息发生变更

  • userListUpdate(userList: UserInfo[], frontRequestId: string): void - 用户列表信息发生变更

  • kickOut(): void - 被踢出房间

  • error(liveMsg: LiveMsg): void - 服务端推送出现异常

live 实例提供了很多诸如live.connect()live.broadcast()live.sendKeyframe()live.exit()live.close()live.dispose()等方法,这些方法都是异步反馈且可能会影响房间内的其他用户。 您可以通过监听以上事件来获知这些影响。

  • connect(options?) - 连接带看房间,建立 WebSocket 连接
  • exit() - 退出带看房间(不发送房间关闭消息)
  • close() - 关闭带看房间(会发送房间关闭消息,通知服务器)
  • dispose() - 销毁带看实例,清理所有资源(事件监听、Yjs 文档等)
  • sendKeyframe(key, frame) - 发送帧数据
  • getFramesByKey(key) - 根据键获取对应的帧数据
  • broadcast(data, toUserIds, timeout?) - 向指定用户广播消息
  • setSelfInfo(userInfo) - 更新本地用户信息
  • toggleMicro(status) - 切换麦克风状态
  • kick(toUserIds) - 踢出指定用户
  • forbidMicro(status, toUserIds?) - 禁止/允许指定用户的麦克风
  • forbidSync(sync, toUserIds?) - 禁止/允许指定用户的帧数据同步
  • updateStatus(evtType, atnType, status, toUserIds?) - 更新带看状态(用户或房间状态)
  • state - 当前带看连接状态(LiveState)
  • readyState - WebSocket 就绪状态
  • url - WebSocket 连接 URL
  • options - 配置选项(只读)
  • snapshot - 当前所有帧数据的快照
  • selfInfo - 本地用户信息
  • userList - 房间内所有用户列表
  • roomInfo - 房间信息
  • $RTC - RTC 实例(用于语音通信)
  • jsBridge - JSBridge 实例(用于 App/WebView 环境)
  • keyframes - 帧数据监听器(Subscribe 实例)

RTC 相关事件通过 live.$RTC.on('/*事件名*/', /*事件回调*/) 方式来监听。

  • error(error: Error): void - 异常事件

  • initWillStart(): void - 即将初始化事件

  • inited(): void - 初始化完成事件

  • joinWillStart(): void - 即将加入语音房间事件

  • joined(info: RTCJoinedInfo): void - 成功加入语音房间事件

  • userVolumes(userVolumes: UserVolume[]): void - 房间内每个说话用户的音量

  • weakNetwork(): void - 弱网提醒事件

比如监听语音房间内正在说话的用户及其音量:

live.$RTC.on('userVolumes', (userVolumes: UserVolume[]) => {
console.log(userVolumes) // userVolumes 正在说话的用户及其音量数据。
})
  • error(liveMsg: LiveMsg): void - 服务端推送出现异常。

与 WebSocket 通信的结构体LiveMsg格式如下:

名称 类型 描述
appId string 应用ID
code string 返回码,详情参照如视开放平台带看文档
command 指令 WebSocket 通信指令类型,详情请查看Live API 文档
data Record<string, any> 本条指令对应的业务数据
frontRequestId string 前端的请求 ID,由 WS 服务原样透传回前端
message string 返回描述
requestId string 后端请求 ID
roomCode string 带看房间号
triggerUserId string 触发指令的用户 ID

当 WebSocket 推送的数据结构体不满足 LiveMsg 或者 code 值不为 SUCCESS 时会以 error 事件的形式抛出来。

  • error(error: Error): void - RTC 语音出现异常。

RTC 语音不同容器或 RTC 解决方案不同,其错误信息内容可能不完全一致。

语音能力需依赖 WebView/Browser 容器,即需要容器端实现 RTC 能力。

为了方便开发者接入,如视开放平台 为主流平台提供 容器 SDKjsbridge-x

Live 通过 jsbridge-x 与 集成了容器 SDK 的客户端应用或微信小程序应用程序桥接,实现原生能力的调用。这个 jsbridge-x 实例需作为配置参数提供给createLive()

如果是 iOS/Android App(已接入如视 VRTC 容器 SDK)

import JSBridge from '@realsee/jsbridge-x'
import { createLive, VRWebViewRTC } from '@realsee/live'

const jsBridgeInstance = new JSBridge()

const rtcInstance = new VRWebViewRTC({
jsBridge: jsBridgeInstance,
getVoiceSign: async (params) => {
// 获取语音签名
const response = await fetch('/api/get-voice-sign', {
method: 'POST',
body: JSON.stringify(params)
})
return response.json()
},
})

const live = createLive({
rtc: rtcInstance,
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
})

提供 RTC 实例配置参数之后,语音的连接、断开、重连等逻辑就托管给了 Live 。您可以通过 live 实例上的 $RTC 命名空间访问语音相关的状态和事件。

// 状态:用户是否成功加入语音房间
live.$RTC.joined
// 状态:用户麦克风状态
live.$RTC.micro
// 状态:当前语音房间ID
live.$RTC.voiceId
// 状态:RTC 类型
live.$RTC.type
// 监听用户音量变化
live.$RTC.on('userVolumes', (userVolumes) => {})

不同于客户端(Android\iOS) 已经内置 RTC 能力,浏览器端需要我们自己安装 第三方 RTC SDK 依赖。 Live 内置 腾讯TRTC 和 亚马逊Chime 两个语音能力集成实现。

纯浏览器端不需要 Native 中转能力,没有 jsBridge 依赖。

安装依赖:

# 需要您自行安装
yarn add trtc-js-sdk
# 或
npm install trtc-js-sdk

使用方式:

import { createLive, BrowserRTC } from '@realsee/live'

const rtcInstance = new BrowserRTC({
getVoiceSign: async (params) => {
const response = await fetch('/api/get-voice-sign', {
method: 'POST',
body: JSON.stringify(params)
})
return response.json()
},
// 可选:处理自动播放策略
// autoPlayConfirm: ({ onConfirm }) => showDialogThen(onConfirm)
})

const live = createLive({
rtc: rtcInstance,
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
})

安装依赖:

# 需要您自行安装
yarn add amazon-chime-sdk-js
# 或
npm install amazon-chime-sdk-js

使用方式:

import { createLive, BrowserRTC4Chime } from '@realsee/live'

const rtcInstance = new BrowserRTC4Chime({
getVoiceSign: async (params) => {
const response = await fetch('/api/get-voice-sign', {
method: 'POST',
body: JSON.stringify(params)
})
return response.json()
},
})

const live = createLive({
rtc: rtcInstance,
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
})

使用方式:

import { createLive, BuiltInRTCType } from '@realsee/live'

const live = createLive({
useBuiltInRTC: true, // 使用内置rtc
builtInRTCType: BuiltInRTCType.RealseeXChimeRTC, // 内置 rtc 类型,不填则默认trtc,chime请指明
useBuiltInJsBridge: true, // 使用内置的jsBridge
getVoiceSign: async (params) => {
// 内置的rtc还是要把 getVoiceSign 方法传递进去
const response = await fetch('/api/get-voice-sign', {
method: 'POST',
body: JSON.stringify(params)
})
return response.json()
},
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
})

// 可以直接使用里面的 jsBridge, 该实例由 @realsee/jsbridge-x 提供支持
const jsBridge = live.jsBridge
jsBridge.ready().then(() => console.info(`${jsBridge.webViewType} jsBridge is ready`))

Live 有完整的 TypeScript GenericsReact Hooks 支持,如果您的技术栈基于 TypeScriptReact 可以参考如下方式更优雅地使用 Live

TypeScript效果演示

步骤一:新建文件 LiveReact.ts 并创建您自己的 LiveReact 实例。

import { createLiveReact } from '@realsee/live'

/** 定义帧数据快照结构 */
interface Snapshot {
/** 以 Five 状态快照为例 */
five: {
panoIndex: number
fov: number
mode: 'Panorama' | 'Model'
latitude: number
longitude: number
}
}

/** 通过 `createLiveReact()` 函数创建实例*/

const LiveReact = createLiveReact<Snapshot>({
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
},
getVoiceSign: async (params) => {
const response = await fetch('/api/get-voice-sign', {
method: 'POST',
body: JSON.stringify(params)
})
return response.json()
},
useBuiltInRTC: true,
})

export default LiveReact

/** 如果您不喜欢 `LiveReact.LiveProvider` 这类编程习惯,可以将需要的函数"抛"出来 */
export const LiveProvider = LiveReact.LiveProvider
export const useConnect = LiveReact.useConnect
export const useKeyframe = LiveReact.useKeyframe
export const useLiveAction = LiveReact.useLiveAction
export const useLiveState = LiveReact.useLiveState
export const useUserList = LiveReact.useUserList
// export ...

步骤二:通过 LiveProviderlive 实例集成到 ReactContext 上下文环境中。

import { LiveProvider } from './LiveReact'

ReactDOM.render(
<LiveProvider>
<RootApp />
</LiveProvider>,
container,
)

步骤三:在您的 React 函数组件中使用相关 Hooks 函数。

Live 提供了丰富的 React Hooks,方便在 React 组件中使用:

  • useLiveInstance() - 获取当前 Live 实例
  • useRTCInstance() - 获取当前 RTC 实例
  • useConnect() - 获取连接函数
  • useKeyframe(key) - 获取指定键的帧数据状态和更新函数(类似 useState)
  • useKeyframeUpdateCallback(key, callback, deps) - 监听指定键的帧数据更新回调
  • useLiveAction() - 获取带看操作函数(connect, sendKeyframe, toggleMicro, broadcast, exit, kick, forbidMicro, forbidSync, getFramesByKey, setSelfInfo, updateStatus)
  • useLiveEventCallback(name, callback, deps) - 监听带看事件
  • useLiveState() - 获取带看连接状态
  • useRTCAction() - 获取 RTC 操作函数(join, quit, toggleMicro, detectMicro, shock)
  • useRTCEventCallback(name, callback, deps) - 监听 RTC 事件
  • useSelfInfo() - 获取和更新用户信息(类似 useState)
  • useKeyframesSnapshot() - 获取所有帧数据快照
  • useUserList() - 获取用户列表

使用示例:

import { useConnect, useLiveAction, useKeyframe, useLiveState, LiveState } from './LiveReact'

function ReconnectBtn() {
const connect = useConnect()
const liveState = useLiveState()

return (
<button
onClick={() => connect({
url: 'wss://example.com/live',
getTicket: async () => {
const response = await fetch('/api/get-ticket')
return response.json().ticket
}
})}
disabled={liveState === LiveState.CONNECTING || liveState === LiveState.OPEN}
>
{liveState === LiveState.OPEN ? '已连接' : '重新连接'}
</button>
)
}

function CameraControl() {
const [camera, setCamera] = useKeyframe('camera')
const { sendKeyframe } = useLiveAction()

const handleMove = () => {
const newCamera = { position: [1, 2, 3], rotation: [0, 0, 0] }
setCamera(newCamera) // 会自动同步到其他用户
// 或者使用 sendKeyframe
// sendKeyframe('camera', newCamera)
}

return (
<div>
<p>相机位置: {camera?.position?.join(', ')}</p>
<button onClick={handleMove}>移动相机</button>
</div>
)
}

欢迎查看 Live API 文档 。
该文档由 TypeDoc 生成,您可以详细查看 API 使用方式、事件 Hooks 及数据结构定义等。