带看tim版SDK指导文档
本文档为 SDK 全量 API 与事件说明,面向接入方开发工程师。
目录
- 概述
- 安装与引入
- 快速开始
- 全量 API 参考
- 初始化配置
- 会议管理
- 带看会话
- 音频控制
- 消息与动作同步
- 成员管理
- 事件监听
- 销毁与清理
- 全量事件说明
- 类型定义全览
- 对接完整流程
- 多平台对接说明
- 后端接口说明
- 常见问题
1. 概述
vr-takelook-plugin 是一个面向 VR 全景带看场景的前端 SDK,提供以下核心能力:
- IM 即时通信:基于腾讯云 TIM,实现成员管理、消息同步、自定义动作传输
- RTC 实时音频:基于腾讯云 TRTC,实现多人语音通话
- VR 同步浏览:通过自定义消息通道,实现多端 VR 画面实时同步
- 多端桥接:自动适配 Web 浏览器、Android WebView、iOS WKWebView
SDK 采用 Facade 模式,对外通过 NestTakelook3(即 GuidedTourFacade 的别名)暴露统一的静态 API,调用方无需关心内部 TIM/TRTC 的初始化细节。
2. 引入
sdk文件地址
2.1 ES Module 引入(推荐)
vr-takelook-plugin位于demo项目的/src/services/takelook3/lib目录中
import { NestTakelook3 } from './vr-takelook-plugin';
// 或使用原始类名
import { GuidedTourFacade } from './vr-takelook-plugin';
2.2 UMD 引入(script 标签)
<script src="path/to/vr-takelook-plugin.umd.cjs"></script>
<script>
const { NestTakelook3 } = window.VrTakelookPlugin;
</script>
2.4 TypeScript 类型导入
import type {
GuidedTourConfig,
StartTourOptions,
GuidedTourStatus,
GuidedTourMember,
GuidedTourNetworkStatus,
GuidedTourCustomAction,
GuidedTourRoleConfig,
GuidedTourBootstrapResult,
GuidedTourBackendEndpoint,
} from 'vr-takelook-plugin';
3. 快速开始
import { NestTakelook3 } from 'vr-takelook-plugin';
// 第一步:设置服务参数
await NestTakelook3.setSDKParams('your-service-token', 'https://your-api.com');
`注意:your-service-token需要你调用开放平台接口获取,具体实现在带看3集成文档中有讲到`
// 第二步:拉取平台配置
await NestTakelook3.getConfig();
// 第三步:注册事件
NestTakelook3.onSdkReady(() => console.log('IM 就绪'));
NestTakelook3.onUserConnected((info) => console.log('已入会:', info));
NestTakelook3.onMemberJoin((member, members) => console.log('成员加入:', member));
NestTakelook3.onMemberExit((member, members) => console.log('成员退出:', member));
NestTakelook3.onEndTour((isMe, reason) => console.log('带看结束', isMe, reason));
NestTakelook3.onRemoteControllingNotify((action) => vrViewer.applyRemoteState(action)); // 同步远端VR相关行为
NestTakelook3.onSyncRemoteCustomActionToLocal((action) => {
console.log('根据发送的参数,同步行为')
})
NestTakelook3.onNetworkStatusChanged((status) => console.log('网络变化:', status));
// 第四步:启动带看
await NestTakelook3.startTour({
userId: 'user_001',
callerUserId: 'agent_001',
meetingId: '12345',
roomId: '12345',
imPlatform: 'web',
});
// 第五步:同步 VR 状态
NestTakelook3.syncLocalActionToRemote({ viewAngle: { x: 0, y: 90 }, sceneId: 'room-1' });
// 第六步:结束带看
await NestTakelook3.endTour('用户主动退出');
4. 全量 API 参考
所有 API 均通过
NestTakelook3静态调用,GuidedTourFacade是其类名别名,两者完全等价。
4.1 初始化配置
setSDKParams(serviceId, baseUrl)
设置 SDK 全局服务参数。必须在所有其他 API 调用之前执行。
await NestTakelook3.setSDKParams(serviceId: string, baseUrl: string): Promise<void>
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
serviceId |
string |
是 | 众趣带看服务 Token,同时作为所有 HTTP 请求的 Authorization 头 |
baseUrl |
string |
是 | 后端 API 基础 URL,例如 https://api.example.com |
getConfig()
从后端拉取平台配置,返回包含 sdkAppId(TIM/TRTC 必需)和角色权限配置的对象。
建议在 setSDKParams 之后、startTour 之前调用。
const config = await NestTakelook3.getConfig(): Promise<unknown>
initAppRemoteSyncer()
手动初始化 App 原生桥接适配器(Android/iOS JSBridge)。
通常无需手动调用,startTour 内部会按需自动初始化。
NestTakelook3.initAppRemoteSyncer(): void
4.2 会议管理
createMeetingRoom(packageId)
向后端申请创建带看会议室,返回后端分配的会议信息。
const result = await NestTakelook3.createMeetingRoom(packageId: string): Promise<unknown>
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
packageId |
string |
是 | 房源/项目 ID |
callOut(userId, packageId)
发起带看呼叫。SDK 通知后端创建会议并触发对端推送,返回包含 meetingId 和 roomId 的对象。
const result = await NestTakelook3.callOut(userId: string, packageId: string): Promise<unknown>
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId |
string |
是 | 发起呼叫的用户 ID |
packageId |
string |
是 | 房源/项目 ID |
getUserSig(userId, packageId, noCheck?, roomId?)
从后端获取用户签名(userSig),用于 TIM/TRTC 鉴权。
startTour 在未提供 userSig 时会自动调用此方法,通常不需要手动调用。
const result = await NestTakelook3.getUserSig(
userId: string,
packageId: string,
noCheck?: boolean,
roomId?: string | number
): Promise<unknown>
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId |
string |
是 | 用户 ID |
packageId |
string |
是 | 房源 ID |
noCheck |
boolean |
否 | 是否跳过后端用量校验 |
roomId |
string \| number |
否 | 指定房间 ID |
leaveConference(userId, roomId, noResponse?)
通知后端用户已离开会议。通常由 endTour 内部自动调用。
const result = await NestTakelook3.leaveConference(
userId: string,
roomId: string | number,
noResponse?: boolean
): Promise<unknown>
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId |
string |
是 | 用户 ID |
roomId |
string \| number |
是 | 房间 ID |
noResponse |
boolean |
否 | true 表示无人接通场景 |
getMeetingId()
获取当前会话的会议 ID。
const meetingId = NestTakelook3.getMeetingId(): string | number | undefined
getUserInfo(userId)
查询指定用户的信息。
const userInfo = await NestTakelook3.getUserInfo(userId: string): Promise<unknown>
4.3 带看会话
startTour(options, initCallback?)
最核心的方法。 启动完整的带看流程:初始化 IM → 登录 TIM → 加入群组 → 建立 RTC 连接。
await NestTakelook3.startTour(
options: StartTourOptions,
initCallback?: () => void
): Promise<void>
StartTourOptions 完整参数说明:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
userId |
string |
是 | 当前用户 ID |
callerUserId |
string |
是 | 带看发起者用户 ID(经纪人端即为自身,客户端填写经纪人 ID) |
imPlatform |
'app' \| 'web' \| 'mp' \| string |
是 | 运行平台,决定 RTC 处理方式(详见下方说明) |
meetingId |
string \| number |
否* | 会议 ID |
roomId |
string \| number |
否* | TRTC 房间 ID |
groupId |
string \| number |
否 | TIM 群组 ID,默认与 roomId 相同 |
serviceId |
string |
否 | 服务 Token,不填则使用 setSDKParams 中的值 |
packageId |
string |
条件必填 | 房源 ID。未提供 userSig 时必填,SDK 将自动请求签名 |
userSig |
string |
否 | 用户签名。若提供则跳过后端签名请求 |
sdkAppId |
string \| number |
否 | TIM/TRTC AppId。若提供则优先使用此值 |
noAudioCall |
boolean |
否 | true 则禁用语音通话(纯 VR 同步模式) |
isInMPWeb |
boolean |
否 | 是否运行在小程序内嵌 WebView 中 |
isWeChatMiniProgram |
boolean |
否 | 是否为微信小程序环境 |
pattern |
string |
否 | 呼叫模式,如 'callout' |
ext |
string |
否 | 自定义扩展参数,原样传递给后端 |
paintContainer |
string |
否 | 画板容器 DOM 选择器(预留能力) |
kfApiURL |
string |
否 | 备选后端 API URL |
* 当未提供
userSig时,SDK 会调用getUserSig自动获取,此时packageId为必填项。
若已有完整的鉴权信息(userSig、sdkAppId、roomId),可全部通过 options 传入,跳过后端请求。
imPlatform 对 RTC 行为的影响:
| imPlatform | RTC 行为 |
|---|---|
'web' |
SDK 内部管理 TRTC Web SDK,在浏览器中直接处理音频采集/播放 |
'app' |
SDK 通过 JSBridge 通知原生壳启动 TRTC,Web 层仅负责 IM 消息 |
'mp' |
通过小程序桥接启动 RTC(预留能力) |
initCallback 参数: 在 SDK 内部完成基础初始化后、真正连接 IM 之前回调,可用于更新 UI 进度。
endTour(reason?, shouldNotify?)
结束带看会话。依次执行:通知后端离会 → 销毁 RTC → 销毁 IM → 清理桥接。
await NestTakelook3.endTour(
reason?: unknown, // 结束原因(任意类型,会透传给 onEndTour 回调)
shouldNotify?: boolean // 是否触发 onEndTour 事件,默认 true
): Promise<void>
getGuidedTourStatus()
获取当前带看状态。
const status = NestTakelook3.getGuidedTourStatus(): GuidedTourStatus
| 枚举值 | 数值 | 说明 |
|---|---|---|
GuidedTourStatus.Idle |
0 |
空闲,未开始带看 |
GuidedTourStatus.Starting |
1 |
正在初始化(startTour 调用后、连接成功前) |
GuidedTourStatus.Connected |
2 |
已连接,带看进行中 |
GuidedTourStatus.Ended |
3 |
已结束 |
4.4 音频控制
voiceForbidden(userId, status)
控制指定用户的静音状态,并通过 IM 消息同步静音指令到远端。
仅在 App 模式下(imPlatform: 'app')有效,Web 模式下需自行处理本地麦克风。
NestTakelook3.voiceForbidden(
userId: string,
status: 0 | 1 // 0 = 取消静音, 1 = 静音
): void
setAudioRoute(route)
设置本地音频输出通道,控制声音从听筒还是扬声器播出。
仅在 App 模式下有效,iOS 原生壳需实现对应处理逻辑。
NestTakelook3.setAudioRoute(
route: 0 | 1 // 0 = 听筒, 1 = 扬声器
): void
4.5 消息与动作同步
syncLocalActionToRemote(vrState)
将本地 VR 浏览状态(视角、当前场景等)通过 IM 群消息广播给所有远端参与者。
通常在 VR 渲染器的视角变化回调中持续调用。
NestTakelook3.syncLocalActionToRemote(
vrState: Record<string, unknown> | undefined
): Record<string, unknown> | undefined
返回值为处理后实际发送的状态对象(或 undefined,如同步被禁用时)。
syncLocalCustomActionToRemote(action)
发送自定义业务动作到远端所有参与者(通过 IM 群消息广播)。
可用于实现任意业务联动逻辑,如切换房源、更换楼层等。
await NestTakelook3.syncLocalCustomActionToRemote(
action: GuidedTourCustomAction
): Promise<void>
// 示例
await NestTakelook3.syncLocalCustomActionToRemote({
action: 'business/changeHouse',
payload: { houseId: 'house-002', floorId: 3 },
});
enableSyncLocalActionToRemote(enabled, isCaller?)
开启或关闭 VR 状态的自动同步功能。
建议在 startTour 完成(onUserConnected 触发)之前关闭,连接成功后再开启,避免在建连过程中产生无效消息。
NestTakelook3.enableSyncLocalActionToRemote(
enabled: boolean,
isCaller?: boolean // 预留参数,当前版本未使用
): void
sendCmd(targetImUserId, msg)
向指定用户发送点对点自定义消息(TIM C2C 消息)。与 syncLocalCustomActionToRemote 的群发不同,此方法为单播。
await NestTakelook3.sendCmd(
targetImUserId: string, // 目标用户的 TIM userId
msg: unknown // 消息内容(任意可序列化对象)
): Promise<void>
receivedCmd(message)
将外部收到的消息注入 SDK 进行解析和分发。
适用于宿主应用有自己独立消息通道(非 SDK 管理的 TIM)的场景,可借助此方法将外部消息路由到 SDK 内部事件系统。
NestTakelook3.receivedCmd(message: unknown): void
4.6 成员管理
kickOutById(userId)
将指定用户踢出当前带看会话(发送 moderation/kickOut 指令)。
NestTakelook3.kickOutById(userId: string): void
getGroupMemberList(options)
查询当前 TIM 群组的成员列表,支持分页。
const result = await NestTakelook3.getGroupMemberList({
groupID: string, // 群组 ID
count?: number, // 每页数量
offset?: number // 分页偏移
}): Promise<unknown>
4.7 事件监听
所有
on*方法均为注册事件监听,支持多次调用注册多个监听器(同一事件的多个回调均会触发)。 例外:onEndTour每次注册前会清除之前注册的监听器,仅保留最新一个。
onSdkReady(cb)
TIM SDK 初始化完成并就绪时触发。表明 IM 连接已建立,可以进行消息收发。
NestTakelook3.onSdkReady(cb: () => void): void
onSdkNotReady(cb)
TIM SDK 变为未就绪状态时触发(如断线、登出)。收到此事件后应停止消息发送操作。
NestTakelook3.onSdkNotReady(cb: () => void): void
onUserConnected(cb)
当前用户加入 TIM 群组成功时触发,回调中携带本用户的基本信息。
NestTakelook3.onUserConnected(cb: (userInfo: {
userID: string;
nickName: string;
avatar: string | undefined;
}) => void): void
onMemberJoin(cb)
有其他成员加入带看会话时触发。
NestTakelook3.onMemberJoin(cb: (
member: GuidedTourMember, // 新加入的成员信息
members?: GuidedTourMember[] // 当前全量成员列表(包含新加入者)
) => void): void
onMemberExit(cb)
有成员退出带看会话时触发。
NestTakelook3.onMemberExit(cb: (
member: GuidedTourMember, // 退出的成员信息
members?: GuidedTourMember[] // 当前全量成员列表(已移除退出者)
) => void): void
onEndTour(cb)
带看会话结束时触发。每次调用 onEndTour 会替换之前注册的回调,只保留最新注册的监听器。
NestTakelook3.onEndTour(cb: (
isMe: boolean, // true = 本端主动调用 endTour 触发;false = 被动结束(对方结束/被踢/断线)
reason?: unknown // 结束原因(由调用 endTour 的一方传入)
) => void): void
onVoiceForbidden(cb)
收到静音/取消静音指令时触发(来源于 voiceForbidden 发出的 IM 指令消息)。
NestTakelook3.onVoiceForbidden(cb: (
userId: string, // 被操作的用户 ID
status: boolean // true = 被静音;false = 取消静音
) => void): void
onKickOut(cb)
当前用户被踢出带看会话时触发(来源于另一端调用了 kickOutById)。
NestTakelook3.onKickOut(cb: () => void): void
onNetworkStatusChanged(cb)
网络质量或连接状态发生变化时触发。可用于展示网络信号指示器或弱网提示。
NestTakelook3.onNetworkStatusChanged(cb: (
status?: GuidedTourNetworkStatus
) => void): void
GuidedTourNetworkStatus 结构:
| 字段 | 类型 | 说明 |
|---|---|---|
level |
'good' \| 'normal' \| 'poor' \| 'offline' |
网络质量等级 |
code |
string \| number |
网络状态码 |
message |
string |
状态描述 |
onSyncRemoteCustomActionToLocal(cb)
收到远端通过 syncLocalCustomActionToRemote 发送的自定义业务动作时触发。
NestTakelook3.onSyncRemoteCustomActionToLocal(cb: (
action: GuidedTourCustomAction, // 动作内容(含 action 名和 payload)
from?: string // 发送方的用户 ID
) => void): void
onRemoteControllingNotify(cb)
收到远端的 VR 浏览状态同步数据时触发(来源于远端调用 syncLocalActionToRemote)。
接收方应在此回调中将 action 数据应用到本地 VR 渲染器。
NestTakelook3.onRemoteControllingNotify(cb: (
action?: unknown // 远端发送的 VR 状态数据(与 syncLocalActionToRemote 传入的 vrState 一致)
) => void): void
onMessageToUI(cb)
SDK 内部需要向 UI 层传递通知、警告或错误信息时触发。可用于显示 Toast 提示等。
NestTakelook3.onMessageToUI(cb: (
message: unknown // 消息内容(通常为字符串或结构化对象)
) => void): void
4.8 销毁与清理
destroy()
完整销毁 SDK 实例,释放所有资源(IM、RTC、桥接),清除所有事件监听。
destroy 内部会自动调用 endTour,无需提前手动结束带看。
await NestTakelook3.destroy(): Promise<void>
5. 全量事件说明
以下是 SDK 事件总线(GuidedTourSignals)中定义的所有事件及其完整回调签名。
标注 ⚠️ 的事件目前在 Facade 上没有对应的 on* 方法,为内部事件或预留事件。
| 事件名 | Facade 方法 | 回调签名 | 触发时机 |
|---|---|---|---|
onSdkReady |
onSdkReady |
() => void |
TIM SDK 初始化完成 |
onSdkNotReady |
onSdkNotReady |
() => void |
TIM SDK 断线/未就绪 |
onConnected |
⚠️ 无 | () => void |
内部连接状态变为已连接(内部使用) |
onEndTour |
onEndTour |
(isMe: boolean, reason?: unknown) => void |
带看会话结束 |
onMemberJoin |
onMemberJoin |
(member: GuidedTourMember, members?: GuidedTourMember[]) => void |
有成员加入 |
onMemberExit |
onMemberExit |
(member: GuidedTourMember, members?: GuidedTourMember[]) => void |
有成员退出 |
onUserConnected |
onUserConnected |
(userInfo: { userID, nickName, avatar }) => void |
当前用户加入群组成功 |
onVoiceForbidden |
onVoiceForbidden |
(userId: string, status: boolean) => void |
收到静音指令 |
onKickOut |
onKickOut |
() => void |
当前用户被踢出 |
onReceiveCard |
⚠️ 无 | (userId: string) => void |
收到名片(预留功能) |
onRequestPhone |
⚠️ 无 | (userId: string, receiverId: string) => void |
收到电话请求(预留功能) |
onResponsePhone |
⚠️ 无 | (userId: string, isApproved: boolean, number?: string) => void |
电话请求响应(预留功能) |
onNetworkStatusChanged |
onNetworkStatusChanged |
(status?: GuidedTourNetworkStatus) => void |
网络状态变化 |
onSyncRemoteCustomActionToLocal |
onSyncRemoteCustomActionToLocal |
(message: GuidedTourCustomAction, from?: string) => void |
收到远端自定义动作 |
onRemoteControllingNotify |
onRemoteControllingNotify |
(action?: unknown) => void |
收到远端 VR 状态同步 |
onChangeHouse |
⚠️ 无 | (houseId: string) => void |
收到切换房源指令(预留功能) |
onMessageToUI |
onMessageToUI |
(message: unknown) => void |
SDK 向 UI 传递通知 |
onGuidePaintVisible |
⚠️ 无 | (visible: boolean) => void |
画板显隐状态变化(预留功能) |
6. 类型定义全览
以下所有类型均可从 vr-takelook-plugin 导入。
GuidedTourStatus(枚举)
enum GuidedTourStatus {
Idle = 0, // 空闲,未开始
Starting = 1, // 正在初始化
Connected = 2, // 已连接,带看中
Ended = 3, // 已结束
}
GuidedTourConfig(接口)
SDK 全局配置结构。
interface GuidedTourConfig {
serviceId?: string; // 服务 Token
baseUrl?: string; // 后端基础 URL
sdkAppId?: string | number; // TIM/TRTC AppId
endpoints?: Partial<Record<GuidedTourBackendEndpoint, string>>; // 自定义接口路径
requestHeaders?: Record<string, string>; // 自定义请求头
imPlatform?: 'app' | 'web' | 'mp' | string; // 运行平台
}
StartTourOptions(接口)
startTour 方法的参数类型。
interface StartTourOptions {
userId: string; // 当前用户 ID(必填)
callerUserId: string; // 带看发起者 ID(必填)
imPlatform: 'app' | 'web' | 'mp' | string; // 运行平台(必填)
serviceId?: string; // 服务 Token
packageId?: string; // 房源 ID
userSig?: string; // 用户签名
sdkAppId?: string | number; // TIM/TRTC AppId
meetingId?: string | number; // 会议 ID
roomId?: string | number; // TRTC 房间 ID
groupId?: string | number; // TIM 群组 ID(默认与 roomId 相同)
noAudioCall?: boolean; // 是否禁用语音
isInMPWeb?: boolean; // 是否在小程序 WebView 中
isWeChatMiniProgram?: boolean; // 是否为微信小程序环境
pattern?: string; // 呼叫模式
ext?: string; // 自定义扩展参数
paintContainer?: string; // 画板容器选择器(预留)
kfApiURL?: string; // 备选后端 URL
}
GuidedTourMember(接口)
带看会话成员信息。
interface GuidedTourMember {
userId: string; // 用户 ID
imUserId?: string; // TIM 用户 ID
role?: string; // 角色(如 agent/customer)
ext?: Record<string, unknown>; // 扩展信息
}
GuidedTourNetworkStatus(接口)
网络状态信息。
interface GuidedTourNetworkStatus {
level?: 'good' | 'normal' | 'poor' | 'offline'; // 质量等级
code?: string | number; // 状态码
message?: string; // 描述信息
}
GuidedTourCustomAction(接口)
自定义业务动作结构。
interface GuidedTourCustomAction {
action: string; // 动作标识符(如 'business/switchScene')
payload?: Record<string, unknown>; // 动作携带的数据
}
GuidedTourBootstrapResult(接口)
SDK 启动结果,包含完整的连接参数。
interface GuidedTourBootstrapResult {
sdkAppId: number; // TIM/TRTC AppId
userSig: string; // 用户签名
meetingId: string; // 会议 ID
roomId: string | number; // 房间 ID
groupId: string | number; // TIM 群组 ID
userId: string; // 用户 ID
targetId?: string; // 目标用户 ID
conversationType?: 'C2C' | 'GROUP'; // 会话类型
roleConfig?: GuidedTourRoleConfig; // 角色配置
raw?: unknown; // 原始后端响应
}
GuidedTourRoleConfig(接口)
角色权限配置。
interface GuidedTourRoleConfig {
[key: string]: unknown;
}
GuidedTourBackendEndpoint(类型)
后端接口端点标识符。
type GuidedTourBackendEndpoint =
| 'getConfig' // 获取平台配置
| 'createMeetingRoom' // 创建会议室
| 'callOut' // 发起呼叫
| 'getUserSig' // 获取用户签名
| 'sendHeartBeat' // 心跳保活
| 'leaveConference' // 离开会议
| 'queryCustomAgentList' // 查询经纪人列表
| 'getUserInfo'; // 获取用户信息
7. 对接完整流程
7.1 调用顺序时序图
发起方(经纪人端) SDK 后端 接收方(客户端)
│ │ │ │
│ 1. setSDKParams() │ │ │
│ ─────────────────────────> │ │ │
│ 2. getConfig() │ │ │
│ ─────────────────────────> │ ── GET /config/ ──────────> │ │
│ │ <── { sdkAppId, ... } ───── │ │
│ 3. 注册所有事件监听 │ │ │
│ ─────────────────────────> │ │ │
│ 4. callOut(userId, pkgId) │ │ │
│ ─────────────────────────> │ ── POST /start/ ──────────> │ │
│ │ <── { meetingId, roomId } ── │ ── 推送通知 ──────────> │
│ │ │ │
│ 5. startTour(options) │ │ │
│ ─────────────────────────> │ ── POST /usersig/ ────────> │ │
│ │ <── { userSig } ─────────── │ │
│ │ [TIM login + joinGroup] │ │
│ │ [TRTC enterRoom] │ │
│ <── onSdkReady ────────── │ │ │
│ <── onUserConnected ───── │ │ │
│ │ │ startTour(options) │
│ │ │ <───────────────────────│
│ <── onMemberJoin ──────── │ ◄───────────────── TIM 群消息 │ │
│ │ │ │
│ 6. syncLocalActionToRemote │ │ │
│ ─────────────────────────> │ ── TIM 群消息 ───────────────────────────────────────> │
│ │ │ │
│ <── onRemoteControlling ─ │ ◄───────────────── TIM 群消息 │ │
│ │ │ │
│ 7. sendHeartBeat()(每30s)│ ── POST /heartbeat/ ──────> │ │
│ │ │ │
│ 8. endTour() │ │ │
│ ─────────────────────────> │ ── POST /user_left/ ──────> │ │
│ │ [TIM quit + logout] │ │
│ │ [TRTC exitRoom] │ │
│ <── onEndTour ──────────── │ │ │
7.2 发起方(经纪人端)完整对接代码
import { NestTakelook3 } from 'vr-takelook-plugin';
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
async function initTour() {
// 步骤一:设置服务参数
await NestTakelook3.setSDKParams('your-service-token', 'https://your-api.com');
// 步骤二:拉取平台配置
await NestTakelook3.getConfig();
// 步骤三:注册事件监听(建议在 startTour 之前注册)
NestTakelook3.onSdkReady(() => {
console.log('IM 就绪');
// 可在此更新 UI 状态
});
NestTakelook3.onSdkNotReady(() => {
console.log('IM 断线');
});
NestTakelook3.onUserConnected((userInfo) => {
console.log('已入会:', userInfo.userID);
// 连接成功后再开启 VR 状态同步
NestTakelook3.enableSyncLocalActionToRemote(true);
});
NestTakelook3.onMemberJoin((member, members) => {
console.log('成员加入:', member.userId);
updateMemberList(members);
});
NestTakelook3.onMemberExit((member, members) => {
console.log('成员退出:', member.userId);
updateMemberList(members);
});
NestTakelook3.onEndTour((isMe, reason) => {
if (heartbeatTimer) clearInterval(heartbeatTimer);
console.log('带看结束:', isMe ? '主动结束' : '被动结束', reason);
navigateToEndScreen();
});
NestTakelook3.onVoiceForbidden((userId, status) => {
console.log(`用户 ${userId} ${status ? '已被静音' : '取消静音'}`);
updateMuteIcon(userId, status);
});
NestTakelook3.onKickOut(() => {
console.log('被踢出会话');
navigateToHome();
});
NestTakelook3.onNetworkStatusChanged((status) => {
if (status?.level === 'poor' || status?.level === 'offline') {
showNetworkWarning(status.level);
}
});
NestTakelook3.onRemoteControllingNotify((action) => {
// 收到对方 VR 操作,同步到本地渲染器
if (action) vrViewer.applyRemoteState(action);
});
NestTakelook3.onSyncRemoteCustomActionToLocal((action, from) => {
switch (action.action) {
case 'business/changeHouse':
loadHouse(action.payload?.houseId as string);
break;
case 'business/switchScene':
switchScene(action.payload?.sceneId as string);
break;
}
});
NestTakelook3.onMessageToUI((message) => {
showToast(String(message));
});
// 步骤四:发起呼叫
const callResult: any = await NestTakelook3.callOut('agent_001', 'package_001');
// 步骤五:禁用同步(连接成功前不发送无效消息)
NestTakelook3.enableSyncLocalActionToRemote(false);
// 步骤六:启动带看
await NestTakelook3.startTour({
userId: 'agent_001',
callerUserId: 'agent_001',
meetingId: callResult.meetingId,
roomId: callResult.roomId,
imPlatform: 'web',
});
// 步骤七:启动心跳保活
heartbeatTimer = setInterval(() => {
const meetingId = NestTakelook3.getMeetingId();
if (meetingId) {
NestTakelook3.sendHeartBeat('agent_001', meetingId);
}
}, 30000);
}
// VR 渲染器视角变化时调用
function onVrViewChange(state: Record<string, unknown>) {
NestTakelook3.syncLocalActionToRemote(state);
}
// 发送自定义业务动作
async function onChangeHouse(houseId: string) {
await NestTakelook3.syncLocalCustomActionToRemote({
action: 'business/changeHouse',
payload: { houseId },
});
}
// 结束带看
async function stopTour() {
if (heartbeatTimer) clearInterval(heartbeatTimer);
await NestTakelook3.endTour('用户主动结束');
// 或完全销毁:await NestTakelook3.destroy();
}
7.3 接收方(客户端)完整对接代码
import { NestTakelook3 } from 'vr-takelook-plugin';
async function joinTour(meetingId: string, roomId: string, myUserId: string) {
// 接收方不需要调用 callOut,meetingId/roomId 通过 App 推送或 URL 参数获取
await NestTakelook3.setSDKParams('your-service-token', 'https://your-api.com');
await NestTakelook3.getConfig();
NestTakelook3.onSdkReady(() => { /* IM 就绪 */ });
NestTakelook3.onUserConnected((info) => {
NestTakelook3.enableSyncLocalActionToRemote(true);
});
NestTakelook3.onMemberJoin((member) => { /* 经纪人加入 */ });
NestTakelook3.onEndTour((isMe, reason) => { /* 带看结束 */ });
// 接收方核心事件:接收经纪人的 VR 控制指令
NestTakelook3.onRemoteControllingNotify((action) => {
vrViewer.applyRemoteState(action);
});
NestTakelook3.onSyncRemoteCustomActionToLocal((action, from) => {
// 处理经纪人发送的业务动作
});
await NestTakelook3.startTour({
userId: myUserId,
callerUserId: 'agent_001', // 经纪人 ID
meetingId,
roomId,
imPlatform: 'app', // 客户端通常在 App WebView 中
packageId: 'package_001', // 若无 userSig 则必填
});
}
8. 多平台对接说明
8.1 Web 浏览器模式(imPlatform: 'web')
SDK 内部完整管理 TIM + TRTC,无需宿主应用做额外处理。
await NestTakelook3.startTour({
...options,
imPlatform: 'web',
});
注意: Web 模式下浏览器需要麦克风权限,请在调用前确保获取了 getUserMedia 授权。
8.2 Android App(WebView)模式(imPlatform: 'app')
IM 在 Web 层运行,RTC(音频采集/播放)委托给原生壳通过 JSBridge 实现。
Web → Native 调用协议:
window.VRViewer.postMessage(JSON.stringify({
action: 'enterRoom', // 或其他 action
params: { roomId, userId, userSig, sdkAppId }
}))
Native → Web 回调协议(原生需注入以下函数):
| 函数名 | 参数 | 触发时机 |
|---|---|---|
window.KFMessageListeners.onEnterRoom(result) |
进房结果对象 | 进入 TRTC 房间成功/失败 |
window.KFMessageListeners.onExitRoom(reason) |
退出原因 | 离开 TRTC 房间 |
window.KFMessageListeners.onError(errCode, errMsg, extInfo) |
错误信息 | TRTC 错误 |
window.KFMessageListeners.onRemoteUserEnterRoom(userId) |
用户 ID | 远端用户进房 |
window.KFMessageListeners.onRemoteUserLeaveRoom(userId, reason) |
用户 ID, 原因 | 远端用户退房 |
window.KFMessageListeners.onFirstAudioFrame(userId) |
用户 ID | 首帧音频到达 |
window.KFMessageListeners.onNetworkQuality(...) |
网络质量数据 | 网络质量上报 |
window.KFMessageListeners.onExitVr() |
无 | 退出 VR(用于触发结束带看) |
8.3 iOS App(WKWebView)模式(imPlatform: 'app')
与 Android 相同,区别在于 JSBridge 通道不同:
Web → Native 调用协议:
window.webkit.messageHandlers.VRViewer.postMessage({
action: 'enterRoom',
params: { roomId, userId, userSig, sdkAppId }
})
Native → Web 回调协议与 Android 相同,使用 window.KFMessageListeners.*。
iOS 独有能力: setAudioRoute 方法可控制听筒/扬声器切换。
8.4 微信小程序 WebView 模式(imPlatform: 'mp')
预留能力,当前框架已搭建,具体实现以实际版本为准,当前暂无
9. 后端接口说明
SDK 依赖的后端接口列表:
| 接口标识 | 方法 | 默认路径 | 说明 |
|---|---|---|---|
getConfig |
GET | /api/v2/op/openapi/takelook/tencent/config/ |
获取平台配置(sdkAppId 等) |
callOut |
POST | /api/v2/op/openapi/takelook/tencent/start/ |
发起呼叫,返回 meetingId/roomId |
getUserSig |
POST | /api/v2/op/openapi/takelook/tencent/usersig/ |
获取用户签名 |
sendHeartBeat |
POST | /api/v2/op/openapi/takelook/tencent/heartbeat/ |
心跳保活 |
leaveConference |
POST | /api/v2/op/openapi/takelook/tencent/user_left/ |
离开会议 |
createMeetingRoom |
POST | /api/v2/op/openapi/takelook/tencent/rooms/allocate/ |
创建会议室 |
queryCustomAgentList |
POST | /api/v2/op/openapi/takelook/tencent/agent_list/ |
查询经纪人列表 |
getUserInfo |
GET | /api/v2/op/openapi/takelook/tencent/user_info/ |
获取用户信息 |
公共请求约定:
- 所有请求携带
Authorization: {serviceId}请求头 - POST 请求 Content-Type 为
application/json - 响应支持直接返回业务数据,或
{ data: { ... } }包裹格式
10. 常见问题
注意: 由于 SDK 本身包含了腾讯 RTC 相关的依赖,部署环境时需要保证域名使用 HTTPS 协议才能正常使用此插件。
Q1:startTour 报错 "Failed to normalize guided tour bootstrap result"
原因: SDK 无法从后端返回中提取 sdkAppId、userSig、roomId 或 meetingId。
解决:
- 确认已正确调用 setSDKParams 和 getConfig
- 检查后端 getUserSig 接口是否返回了 userSig 和 sdkAppId 字段
- 或在 startTour 参数中直接提供 userSig、sdkAppId、roomId 跳过后端请求
Q2:App 模式下没有声音
原因: imPlatform: 'app' 时音频由原生壳管理,SDK 本身不处理音频。
排查:
- Android:确认原生壳实现了 window.VRViewer.postMessage,且正确处理了 enterRoom 消息
- iOS:确认实现了 window.webkit.messageHandlers.VRViewer
- 检查 onEnterRoom 回调是否返回了成功状态
- 检查原生侧是否有麦克风/扬声器权限
Q3:onMemberJoin / onMemberExit 不触发
原因: 成员事件依赖 TIM 群消息推送。
排查:
- 确认 roomId/groupId 正确,双方加入了同一个 TIM 群组
- 确认服务端已广播 takelook_member_joined / takelook_member_left 类型的群消息
- 确认 TIM 群类型为 Meeting(SDK 默认使用 Meeting 类型群组)
Q4:如何实现仅 VR 同步、不开启语音?
await NestTakelook3.startTour({
...options,
noAudioCall: true, // 禁用语音,仅同步 VR 画面
});
Q5:如何避免连接建立前发送无效 VR 同步消息?
// startTour 前关闭同步
NestTakelook3.enableSyncLocalActionToRemote(false);
// 连接成功后再开启
NestTakelook3.onUserConnected(() => {
NestTakelook3.enableSyncLocalActionToRemote(true);
});
Q6:宿主应用有自己的消息通道,如何与 SDK 共用?
// 方式一:将外部收到的消息注入 SDK 解析
externalChannel.onMessage((msg) => {
NestTakelook3.receivedCmd(msg);
});
// 方式二:用 sendCmd 发送点对点消息(不经过 SDK 群消息通道)
await NestTakelook3.sendCmd('target_user_id', { type: 'custom', data: '...' });
附录 A:内部消息协议
SDK 所有 TIM 消息均封装在统一信封中:
{
"namespace": "nest-takelook3",
"category": "cmd | viewerAction | customAction",
"payload": { }
}
| category | 说明 | 对应 API |
|---|---|---|
cmd |
底层指令(静音、踢人等) | sendCmd(), voiceForbidden(), kickOutById() |
viewerAction |
VR 浏览状态同步 | syncLocalActionToRemote() |
customAction |
自定义业务动作 | syncLocalCustomActionToRemote() |
SDK 自动过滤 namespace ≠ 'nest-takelook3' 的消息,不影响宿主应用自身的 TIM 消息处理。
附录 B:SDK 内置保留动作名
以下动作名为 SDK 内部使用,接入方自定义动作时请勿使用这些名称作为前缀:
| 动作名 | 说明 | 触发 API |
|---|---|---|
member/mute |
静音指令 | voiceForbidden() 内部发送 |
moderation/kickOut |
踢出指令 | kickOutById() 内部发送 |
viewer/syncHouseState |
VR 状态同步 | syncLocalActionToRemote() 内部发送 |