import { JSBridgeProtocol } from '@realsee/jsbridge-x'; import { RTCProtocol } from '../rtc'; import { EventType, KeyframeEventType, LiveOptions, RoomInfo, RTCType, UserInfo, BuiltinEventType, BuiltinActionType, MicroStatus, LiveConnectOptions } from '../typings'; import { LiveState } from '../typings/live/LiveState'; import { Subscribe } from '../utils/subscribe'; import * as Y from 'yjs'; export type LiveBuiltInData = { selfInfo?: UserInfo; userList?: UserInfo[]; roomInfo?: RoomInfo; rtcInfo?: { voiceId: string; type: RTCType; }; keyframes: Y.Map; }; export declare const _clearLiveInstanceIfNeed: () => void; /** * Live 带看实例类 * * 这是带看功能的核心类,负责管理 WebSocket 连接、帧数据同步、RTC 语音通信等功能。 * 通过 Yjs 实现帧数据的协同编辑和同步。 * * @template KeyframesSnapshot 帧数据快照类型,必须是一个对象类型 * @example * ```typescript * // 定义帧数据类型 * interface MyKeyframes { * camera: { position: [number, number, number] } * annotation: { text: string; position: [number, number] } * } * * // 创建 Live 实例 * const live = new Live({ * url: 'wss://example.com/live', * getTicket: async () => 'your-ticket', * getVoiceSign: async (params) => ({ sdkAppId: 0, userId: '', userSig: '', roomId: '' }), * useBuiltInRTC: true * }) * * // 监听状态变化 * live.on('stateChange', (state, prevState) => { * console.log(`状态从 ${prevState} 变为 ${state}`) * }) * * // 连接带看 * await live.connect() * ``` */ export declare class Live> extends Subscribe> { /** * 带看的配置选项 */ private _options; /** * 获取带看的配置选项(只读) * @returns 冻结的配置选项对象 */ get options(): LiveOptions; /** * 获取当前带看连接状态 * @returns 当前连接状态 */ get state(): LiveState; /** * 兼容老逻辑 * @deprecated 请直接读取 state 属性字段,未来将移除该属性 * @returns 当前连接状态 */ get readyState(): LiveState; /** * 获取当前WebSocket连接的URL。(注意:这个是WebSocket连接的完整连接,附带一些query参数)。 * @deprecated 带看不需要关注底层ws的相关信息,未来将移除 * @returns WebSocket 连接 URL,如果未设置则返回 undefined */ get url(): string | undefined; /** * 获取当前 live 实例使用的 RTC 实例 * @returns RTC 协议实例,用于语音通信 */ get $RTC(): RTCProtocol; /** * 获取当前 live 实例使用的 jsBridge 实例 * @returns JSBridge 协议实例,用于与原生 App 通信 */ get jsBridge(): JSBridgeProtocol; /** * 帧数据监听器 * * 用于监听特定键的帧数据变化。当其他用户发送帧数据更新时,会触发对应键的事件。 * * @example * ```typescript * // 监听 camera 帧数据的变化 * live.keyframes.on('camera', (nextFrame, prevFrame, frontRequestId) => { * console.log('相机位置更新:', nextFrame) * }) * ``` */ keyframes: Subscribe>; /** * 带看的 Yjs 文档实例(用于帧数据同步) */ private _yDoc; /** * 内置的一些数据(用户信息、房间信息、帧数据等) */ private _buildInData; /** * 创建 Live 带看实例 * * @param opts 带看配置选项,包含以下属性: * - url: WebSocket 连接地址(可选,可在 connect 时提供) * - getTicket: 获取连接票据的函数(可选,可在 connect 时提供) * - rtc: RTC 实例(可选,如果不提供会根据配置自动创建) * - rtcType: 指定内置 RTC 类型(当 rtc 为空时使用) * - useBuiltInRTC: 是否使用内置 RTC(默认根据环境自动选择) * - getVoiceSign: 获取语音签名的函数(使用内置 RTC 时必需) * - jsBridge: JSBridge 实例(可选,用于 App/WebView 环境) * - useBuiltInJsBridge: 是否使用内置 JSBridge(默认 false) * - initialMicroStatus: 麦克风初始状态(默认 OPEN) * - autoPlayConfirm: 自动播放确认回调(使用 BrowserRTC 时可能需要) * - __debug__: 是否开启调试模式(会在控制台打印日志) * * @example * ```typescript * // 基础用法 * const live = new Live({ * url: 'wss://example.com/live', * 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 * }) * * // 使用自定义 RTC * const customRTC = new MyCustomRTC() * const live = new Live({ * rtc: customRTC, * url: 'wss://example.com/live', * getTicket: async () => 'ticket' * }) * ``` */ constructor(opts: LiveOptions); /** * 获取当前所有帧数据的快照 * * @returns 当前帧数据的部分快照对象 * @example * ```typescript * const snapshot = live.snapshot * console.log('当前相机位置:', snapshot.camera) * console.log('当前标注:', snapshot.annotation) * ``` */ get snapshot(): Partial; /** * 初始化帧数据观察者(内部方法) * 用于监听 Yjs Map 的变化并同步到其他用户 */ _initializeKeyframesObservers: () => void; /** * 处理帧数据变化(内部方法) * 当本地或远程帧数据发生变化时,会触发此方法进行同步 * * @param event Yjs Map 事件对象 */ _handleKeyframesChange: (event: Y.YMapEvent) => void; /** * 根据键值获取对应的帧数据 * * @template Key 帧数据的键类型 * @param key 帧数据的键值 * @returns 对应键的帧数据,如果不存在则返回 undefined * @example * ```typescript * // 获取相机帧数据 * const cameraFrame = live.getFramesByKey('camera') * if (cameraFrame) { * console.log('相机位置:', cameraFrame.position) * } * ``` */ getFramesByKey: (key: Key) => Partial[Key] | undefined; /** * 发送帧数据(需要能够被序列化) * * 将帧数据同步到其他用户。帧数据必须是可序列化的 JSON 对象。 * 只有在连接状态为 OPEN 时才能发送。 * * @template Key 帧数据的键类型 * @param key 帧数据键值 * @param frame 帧数据本体,必须可序列化 * @returns 发送结果,成功返回 true,连接未打开返回 false,其他错误返回 Error * @example * ```typescript * // 发送相机位置更新 * const result = live.sendKeyframe('camera', { * position: [1, 2, 3], * rotation: [0, 0, 0] * }) * * if (result === true) { * console.log('发送成功') * } else { * console.error('发送失败:', result) * } * * // 发送标注更新 * live.sendKeyframe('annotation', { * text: '这是一个标注', * position: [100, 200] * }) * ``` */ sendKeyframe: (key: Key, frame: KeyframesSnapshot[Key]) => boolean | Error; /** * 获取当前用户信息 * * @returns 当前用户信息对象,如果尚未获取则返回 undefined * @example * ```typescript * const selfInfo = live.selfInfo * if (selfInfo) { * console.log('用户ID:', selfInfo.id) * console.log('麦克风状态:', selfInfo.micro_status) * console.log('用户权限:', selfInfo.permission) * } * ``` */ get selfInfo(): UserInfo | undefined; /** * 获取当前房间内的用户列表 * * @returns 用户信息数组,如果尚未获取则返回空数组 * @example * ```typescript * const users = live.userList * console.log(`房间内有 ${users.length} 个用户`) * users.forEach(user => { * console.log(`用户 ${user.id} 在线状态: ${user.current_ws_online}`) * }) * ``` */ get userList(): UserInfo[]; /** * 获取当前带看房间信息 * * @returns 房间信息对象,如果尚未获取则返回 undefined * @example * ```typescript * const roomInfo = live.roomInfo * if (roomInfo) { * console.log('房间代码:', roomInfo.info.room_code) * console.log('房间状态:', roomInfo.info.status) * } * ``` */ get roomInfo(): RoomInfo | undefined; /** * 带看控制器(内部使用) */ private _controller; /** * 初始化配置选项(内部方法) * @param opts 配置选项 */ private _initializeOptions; /** * 初始化 JSBridge 实例(内部方法) * @param opts 配置选项 * @returns JSBridge 实例或 undefined */ private _initializeJsBridge; /** * 初始化 RTC 实例(内部方法) * @param opts 配置选项 * @returns RTC 实例或 undefined */ private _initializeRTC; /** * 创建内置 RTC 实例(内部方法) * @param opts 配置选项 * @returns RTC 实例或 undefined */ private _createBuiltInRTC; /** * 创建 VRWebView RTC 实例(内部方法) * @returns RTC 实例或 undefined */ private _createVRWebViewRTC; /** * 创建浏览器 RTC 实例(内部方法) * @returns RTC 实例或 undefined */ private _createBrowserRTC; /** * 创建默认 RTC 实例(内部方法) * 根据环境自动选择合适的 RTC 实现 * @param opts 配置选项 * @returns RTC 实例或 undefined */ private _createDefaultRTC; /** * 自动连接(内部方法) * 如果配置中已提供 url 和 getTicket,则自动连接 */ private _autoConnect; /** * 调试模式设置(内部方法) * 在调试模式下,将实例挂载到 window 对象上 */ private _debugSetup; /** * 添加带看控制的事件监听 */ private _addLiveControllerEventListeners; /** * 移除带看控制的事件监听 */ private _removeLiveControllerEventListeners; /** * 带看状态变化处理 * @param state 当前状态 * @param prevState 之前的状态 */ private _handleStateChange; /** * 消息接收 * @param liveMsg */ private _handleMessage; /** * 清理带看缓存的数据 */ private _clearLiveData; /** * 连接带看房间 * * 建立 WebSocket 连接并加入带看房间。如果配置中已提供 url 和 getTicket, * 可以在创建实例后自动连接,也可以手动调用此方法。 * * @param options 连接选项,包含以下属性: * - url: WebSocket 连接地址(可选,会覆盖构造函数中的配置) * - getTicket: 获取连接票据的函数(可选,会覆盖构造函数中的配置) * - rtcMicStatus: 麦克风初始状态(可选,会覆盖构造函数中的配置) * - force: 是否强制连接(如果已有连接,会先关闭再连接) * @returns 连接结果,成功返回 true,失败返回 Error * @example * ```typescript * // 在创建实例时已配置 url 和 getTicket * const live = new Live({ * url: 'wss://example.com/live', * getTicket: async () => 'ticket' * }) * * // 手动连接 * const result = await live.connect() * if (result === true) { * console.log('连接成功') * } else { * console.error('连接失败:', result.message) * } * * // 在连接时提供配置 * await live.connect({ * url: 'wss://example.com/live', * getTicket: async () => { * const res = await fetch('/api/ticket') * return res.json().ticket * }, * rtcMicStatus: MicroStatus.OPEN * }) * * // 强制重连 * await live.connect({ force: true }) * ``` */ connect: (options?: LiveConnectOptions) => Promise; /** * 退出带看(内部方法) * 清理数据并关闭连接,但不发送房间关闭消息 * * @returns Promise 关闭是否成功 */ _exit: () => Promise; /** * 退出带看房间 * * 这是 close 方法的别名,用于保持 API 的一致性。 * * @returns Promise * @example * ```typescript * // 退出带看 * await live.exit() * ``` */ exit: () => Promise; /** * 关闭带看房间 * * 关闭 WebSocket 连接、退出 RTC 房间,并发送房间关闭消息。 * 与 exit 方法不同的是,此方法会通知服务器房间已关闭。 * * @returns Promise * @example * ```typescript * // 关闭房间 * await live.close() * * // 在组件卸载时关闭 * useEffect(() => { * return () => { * live.close() * } * }, []) * ``` */ close: () => Promise; /** * 销毁带看实例 * * 完全销毁实例,清理所有资源,包括事件监听、Yjs 文档等。 * 销毁后实例不能再使用,需要重新创建。 * * @returns Promise * @example * ```typescript * // 销毁实例 * await live.dispose() * * // 在组件卸载时销毁 * useEffect(() => { * return () => { * live.dispose() * } * }, []) * ``` */ dispose: () => Promise; /** * 更新带看状态 * * 发送内置消息来更新用户或房间的状态。只有在连接状态为 OPEN 时才能发送。 * * @param evtType 事件类型(用户变化或房间变化) * @param atnType 操作类型(如用户信息、麦克风状态、房间关闭等) * @param status 状态数据对象 * @param toUserIds 目标用户ID(可选,不传则广播给所有人) * @returns 是否发送成功,连接未打开或发送失败返回 false * @example * ```typescript * // 更新用户信息 * await live.updateStatus( * BuiltinEventType.UserChange, * BuiltinActionType.UserInfo, * { nickname: '新昵称', avatar: 'https://example.com/avatar.jpg' } * ) * * // 关闭房间(仅管理员可用) * await live.updateStatus( * BuiltinEventType.RoomChange, * BuiltinActionType.RoomClose, * {} * ) * * // 向特定用户发送状态更新 * await live.updateStatus( * BuiltinEventType.UserChange, * BuiltinActionType.UserMicroStatus, * { status: MicroStatus.CLOSE }, * ['user-id-1', 'user-id-2'] * ) * ``` */ updateStatus(evtType: BuiltinEventType, atnType: BuiltinActionType, status: Record, toUserIds?: string | string[]): Promise; /** * 设置自己的用户信息 * * 更新当前用户在房间中的信息,如昵称、头像等。 * * @param userInfo 用户信息的部分更新对象 * @returns Promise 是否更新成功 * @example * ```typescript * // 更新昵称和头像 * await live.setSelfInfo({ * extension: { * nickname: '新昵称', * avatar: 'https://example.com/avatar.jpg' * } * }) * ``` */ setSelfInfo: (userInfo: Partial) => Promise; /** * 自定义广播消息 * * 向指定用户发送自定义消息,并等待响应。如果设置了超时时间, * 超时后会拒绝 Promise。 * * @template Request 请求数据类型 * @template Response 响应数据类型 * @param request 广播请求的数据对象 * @param toUserIds 目标用户ID,可以是单个字符串或字符串数组,空数组表示广播给所有人 * @param timeout 超时时间(毫秒),0 表示不超时 * @returns Promise 响应数据,连接未打开返回 false,发送失败返回 Error * @example * ```typescript * // 发送自定义消息并等待响应 * try { * const response = await live.broadcast( * { type: 'ping', data: 'hello' }, * ['user-id-1', 'user-id-2'], * 5000 // 5秒超时 * ) * console.log('收到响应:', response) * } catch (error) { * console.error('广播失败或超时:', error) * } * * // 广播给所有人 * const response = await live.broadcast( * { type: 'announcement', message: '大家好' }, * [], * 0 // 不超时 * ) * * // 监听广播消息 * live.on('broadcast', (data, frontRequestId) => { * console.log('收到广播:', data) * // 可以在这里响应广播 * }) * ``` */ broadcast: , Response>(request: Request, toUserIds: string | string[], timeout: number) => Promise | Error | false; /** * 切换自己的麦克风状态 * * 开启或关闭自己的麦克风。只有拥有 control_own_micro 权限的用户才能操作。 * * @param microStatus 麦克风状态,true 表示开启,false 表示关闭 * @returns Promise 是否操作成功,无权限返回 false * @example * ```typescript * // 开启麦克风 * const result = await live.toggleMicro(true) * if (result) { * console.log('麦克风已开启') * } * * // 关闭麦克风 * await live.toggleMicro(false) * * // 检查权限 * if (live.selfInfo?.permission?.control_own_micro) { * await live.toggleMicro(true) * } else { * console.log('没有权限控制麦克风') * } * ``` */ toggleMicro: (microStatus: boolean) => Promise; /** * 控制其他用户的麦克风状态 * * 开启或关闭指定用户的麦克风。只有拥有 control_other_micro 权限的用户才能操作。 * * @param microStatus 麦克风状态(MicroStatus.OPEN 或 MicroStatus.CLOSE) * @param toUserIds 目标用户ID,可以是单个字符串或字符串数组 * @returns Promise 是否操作成功,无权限或权限检查失败返回 false * @example * ```typescript * // 关闭某个用户的麦克风 * await live.forbidMicro(MicroStatus.CLOSE, 'user-id-1') * * // 开启多个用户的麦克风 * await live.forbidMicro(MicroStatus.OPEN, ['user-id-1', 'user-id-2']) * * // 检查权限 * if (live.selfInfo?.permission?.control_other_micro) { * await live.forbidMicro(MicroStatus.CLOSE, 'user-id-1') * } * ``` */ forbidMicro: (microStatus: MicroStatus, toUserIds: string | string[]) => Promise; /** * 控制其他用户的帧数据同步权限 * * 允许或禁止指定用户同步全量帧数据。只有拥有 control_other_sync 权限的用户才能操作。 * * @param sync 是否允许同步,true 表示允许,false 表示禁止 * @param toUserIds 目标用户ID,可以是单个字符串或字符串数组 * @returns Promise 是否操作成功,无权限或权限检查失败返回 false * @example * ```typescript * // 禁止某个用户同步帧数据 * await live.forbidSync(false, 'user-id-1') * * // 允许多个用户同步帧数据 * await live.forbidSync(true, ['user-id-1', 'user-id-2']) * ``` */ forbidSync: (sync: boolean, toUserIds: string | string[]) => Promise; /** * 将用户踢出带看房间 * * 强制将指定用户从房间中移除。只有拥有 change_other_status 权限的用户才能操作。 * * @param toUserIds 目标用户ID,可以是单个字符串或字符串数组 * @returns Promise 是否操作成功,无权限或权限检查失败返回 false * @example * ```typescript * // 踢出单个用户 * await live.kick('user-id-1') * * // 踢出多个用户 * await live.kick(['user-id-1', 'user-id-2']) * * // 检查权限 * if (live.selfInfo?.permission?.change_other_status) { * await live.kick('user-id-1') * } * ``` */ kick: (toUserIds: string | string[]) => Promise; /** * 获取指定键的帧数据快照 * * 这是 getFramesByKey 方法的别名,用于保持 API 的一致性。 * * @template Key 帧数据的键类型 * @param key 帧数据的键值 * @returns 对应键的帧数据,如果不存在则返回 undefined * @example * ```typescript * const cameraFrame = live.getSnapshotForKey('camera') * ``` */ getSnapshotForKey: (key: Key) => any; }