API 参考
LiveSDK 类
Section titled “LiveSDK 类”new LiveSDK(config: SDKConfig): LiveSDK必填字段缺失时抛出异常:activityId、token、merchantId。
SDKConfig
Section titled “SDKConfig”interface SDKConfig { /** 直播间 ID */ activityId: string
/** JWT 访问令牌(由 auth 服务颁发) */ token: string
/** 商户 ID */ merchantId: string
/** 门店 ID;房间营销 cmd 6010 上报透传,缺省按 0 */ storeId?: number
/** HTTP API host;运行时优先,缺失时才读取 VITE_API_ENDPOINT */ api?: SDKApiConfig
/** 统一端点;优先于兼容字段与构建环境变量 */ endpoints?: EndpointConfig
/** 可信后端签发的观看会话交接上下文;缺失时可靠任务上报保持关闭 */ watchSession?: WatchSessionConfig
/** mount 认证的房间详情启动策略;未配置时保持 SDK 自拉详情 */ bootstrap?: SDKBootstrapConfig
/** BCP-47 locale 字符串,用于 UI 文案。默认 'zh-CN'。未知值警告后回退。 */ locale?: string
/** Phase 11:启用哪些 WS 驱动入口模块。默认全部启用。NEEDS-SERVER-VERIFY */ entryModules?: AppEntryModuleId[]
/** 腾讯 IM 配置(Phase 7) */ tim?: TIMConfig
/** @deprecated 改用 ws.url */ wsUrl?: string
/** LAPS WebSocket 配置(Phase 10)。未配置时降级为 HTTP 轮询。 */ ws?: WSConfig
/** 启用/禁用特定业务模块(默认全部启用) */ modules?: ModuleConfig[]
/** 内置 app 模块(ViewerCount / Like / Gift / Goods / Security)的开关 */ appModules?: AppModuleConfigs
/** 布局配置(横竖屏、Tab、模块顺序)*/ layout?: LayoutConfig
/** * 业务功能配置(FR-FC-02)—— 业务 UI 可见性统一收口(按钮/入口是否渲染)。 * 与 appModules(WS 数据模块开关)正交。运行时可经 updateFeatures/setFeature 增量更新。 * 零配置 = 今日行为。详见 FeatureConfig。 */ features?: FeatureConfig
options?: SDKOptions
/** * 宿主注入 IM 用户资料(昵称 / 头像)。 * 等级 levelId 由 auth 返回的 ve/token.agentLevelId 自动写入,宿主不传。 */ user?: { nickName?: string, avatar?: string }
/** * 宿主注入的能力 handler(抛出给宿主实现)。 * 当前仅 redPacket(微信/账户写操作);未注入对应 handler 时 SDK 弹窗优雅降级。 */ handlers?: { /** 我的红包:提现 / 待收款确认(微信 JSAPI 由宿主处理) */ redPacket?: RedPacketHandlers }}SDKApiConfig / SDKBootstrapConfig
Section titled “SDKApiConfig / SDKBootstrapConfig”interface SDKApiConfig { /** 协议 + host;内部会 trim 并移除尾斜杠 */ baseUrl?: string}
interface RoomDetailBootstrap { source: 'host-cache' activityId: string merchantId: string detail: RoomDetailBody}
interface SDKBootstrapConfig { /** 默认 sdk-fetch;required 时 seed 缺失或非法也不回退请求详情 */ roomDetailPolicy?: 'sdk-fetch' | 'required' roomDetail?: RoomDetailBootstrap}EndpointConfig 为 { apiBaseUrl?, wsUrl?, watchReportBaseUrl? }。解析顺序为 setEndpoints() → SDKConfig.endpoints → api.baseUrl/ws.url/wsUrl → 构建变量。ordinary/Range 使用各自 SDK 内置 path,拼接时保留 SaaS base pathname;拒绝跨域、query/hash、凭据及路径穿越。setEndpoints(partial):Promise<this> 可在 mount 前后调用;getEndpoints() 返回当前规范化快照。
WatchSessionConfig 为 { watchSessionId, predecessorSessionId?, handoffCapability, predecessorFinalization },capability 为 'fence_v1'|'none',finalization 为 'successor_authorized'|'current_session_only'。该对象必须由可信登录/bootstrap 服务签发,浏览器不得自行生成 predecessor。
API 缺失时 fail-closed,WS 缺失时降级轮询,上报地址缺失时不发送。HTTP/上报仅接受 https://,WS 仅接受 wss://;输入会 trim 并移除尾 /。
红包 handler(SDKConfig.handlers.redPacket)
Section titled “红包 handler(SDKConfig.handlers.redPacket)”「我的红包」弹窗自包含余额展示与红包/提现记录列表(SDK 自调账户域 HTTP,仅需 token); 微信 JSAPI 写操作(提现申请状态机、授权绑定、待收款二次确认)整体抛给宿主实现。 SDK 调用后 await 返回归一化结果,据此自动刷新余额与提现记录。
interface RedPacketHandlers { /** 发起提现(含可能的微信授权绑定子流程,宿主全权处理)。全额提现,amount 由 SDK 裁剪(≤200)。 */ withdraw?: (input: { amount: number }) => Promise<{ status: 'success' | 'cancelled' | 'fail' | 'pending' // pending=已提交待微信到账 message?: string // fail 时展示给用户 }> /** 待收款二次确认(提现记录里 WAIT_USER_CONFIRM 状态的「待收款」按钮,对应微信 onConfirmPay)。 */ confirmReceipt?: (record: RedWithdraw) => Promise<{ status: 'success' | 'cancelled' | 'fail' | 'pending', message?: string }>}未注入
withdraw→ 提现按钮禁用 + 提示「未配置提现能力」;明细列表仍可用。 未注入confirmReceipt→ 提现记录「待收款」按钮不可点。 金额规则:余额 <0.03 拦截不发;>200 裁剪为本次 200(微信单次上限)。
const sdk = new LiveSDK({ activityId, token, merchantId, handlers: { redPacket: { withdraw: async ({ amount }) => { // 宿主实现:applyWithdrawV2 + onConfirmPay + sync 状态机 // … return { status: 'success' } }, confirmReceipt: async (record) => { // 宿主实现:onConfirmPay(record) return { status: 'success' } }, }, },})也可命令式打开:sdk.openRedPacket()(等价点击「我的红包」按钮)。
宿主用户资料(IM 评论身份)
Section titled “宿主用户资料(IM 评论身份)”mount 时 SDK 将 config.user 与 JWT 昵称合并后同步到腾讯 IM(updateMyProfile),发评时在 cloudCustomData 附带 { levelId }(来自 ve/token.agentLevelId,无则为 null)。收端用 memberLevels 匹配展示等级徽章。
昵称优先级:config.user.nickName / 运行时 setProfile.nickName > JWT resolveImUserNickName > userId。
头像:宿主 config.user.avatar / setProfile.avatar 优先;空或未传时回退 DEFAULT_AVATAR(https://image.jbzyun.cn/statics/image/def-avatar.png),避免 IM profile / 评论区空头像。
const sdk = new LiveSDK({ activityId, token, merchantId, user: { nickName: '小明', avatar: 'https://cdn.example.com/avatar.png' },})await sdk.mount('#live')
// 进房后动态更新await sdk.getModule<User>('user')?.setProfile({ nickName: '新昵称', avatar: 'https://...' })
updateMyProfile失败仅console.warn,不阻塞发评;levelId不可由宿主注入。
TIMConfig
Section titled “TIMConfig”interface TIMConfig { sdkAppId: number // 腾讯 IM App ID userId: string // 观众用户 ID userSig: string // 服务端生成的 UserSig groupId: string // 直播间 IM 群组 ID}注意:TIM 凭证由集成方通过环境变量或运行时注入,auth API 不返回这些字段。
WSConfig
Section titled “WSConfig”interface WSConfig { url: string // wss://your-laps-host/ws reconnectBaseDelay?: number // 重连基础延迟 ms(默认 1000) reconnectMaxDelay?: number // 重连最大延迟 ms(默认 30000) reconnectMaxAttempts?: number // 最大重连次数(默认 10) heartbeatInterval?: number // 心跳间隔 ms(默认 25000) authTimeout?: number // 认证超时 ms(默认 5000) loginRetryMax?: number // 认证重试次数(默认 3)}注意:WS 端点由集成方提供,auth API 不返回 WS URL。解析顺序为
ws.url→ 已废弃的wsUrl→ 构建期VITE_WS_URL;均未配置时不连接 WS、降级 HTTP 轮询,没有内置测试地址兜底。
SDKOptions
Section titled “SDKOptions”interface SDKOptions { origin?: string // 保留字段;当前不参与 HTTP 地址解析,请使用 SDKConfig.api.baseUrl saveUserInfo?: boolean logLevel?: 'silent' | 'error' | 'warn' | 'info' | 'debug' // 默认 'warn' /** 主题覆盖 — 仅传入字段覆盖 CSS 默认值 */ theme?: SDKTheme /** 运行时 i18n 字符串覆盖,叠加在 locale 基础字符串之上 */ i18n?: Partial<I18nMessages> /** * image-text-float 浮窗图标可见性锁。 * 受控 key:QUESTION / QUESTIONNAIRE / CHECKIN / LOTTERY / LUCKYMONEY / COUPON。 * 未配置或空数组默认可见;key 支持可选 SYS: 前缀。 */ floatingInteractionIcons?: { key: string, visible?: boolean }[] /** 商品营销行为;默认 false 以兼容仍从 goods.promotion.click 自开的宿主 */ goodsMarketing?: { autoOpenExchangeCoupon?: boolean }}SDKTheme(全部可选)
Section titled “SDKTheme(全部可选)”interface SDKTheme { primaryColor?: string // 主色(按钮、高亮) backgroundColor?: string // 背景色 overlayColor?: string // 浮层背景色 chatBgColor?: string // 聊天区域背景色 textColor?: string // 文字颜色 fontFamily?: string // 字体族 fontSize?: string // 基础字号(如 '14px') radius?: string // 圆角(如 '8px')}CSS 变量以 --live-* 前缀注入,默认值定义在 .livesdk-root(app.css)中。
LayoutConfig
Section titled “LayoutConfig”interface LayoutConfig { /** * 方向控制。 * - 'portrait' | 'landscape' → 强制指定,忽略 API 返回值 * - 'auto'(默认)→ 读取 body.extendSet.screenType(1=横屏,2=竖屏) */ orientation?: 'portrait' | 'landscape' | 'auto' chatTabs?: { key: string; label: string }[] modulesOrder?: { chatOffset?: number }}AppEntryModuleId
Section titled “AppEntryModuleId”type AppEntryModuleId = | 'roomMarketing' // 房间营销统一入口(签到 / 福袋 / 券) | 'lottery' | 'taskReward' | 'productList' | 'commentary' | 'goodsMarketing'⚠️ 旧
coupon/imageTextFloat/checkInentry 已废止,统一并入roomMarketing。
FeatureConfig(业务特性配置,FR-FC-01)
Section titled “FeatureConfig(业务特性配置,FR-FC-01)”业务 UI 可见性根类型。namespace 分组(topbar / goods / more),leaf key = 事件键去首段命名空间(保留后续点)。零配置 = 今日行为。
interface FeatureConfig { /** TopBar 右上角按钮组可见性 */ topbar?: { /** 个人中心入口(emit topbar.userCenter)。默认 true */ userCenter?: FeatureItemOptions /** 经营信息入口(emit topbar.businessInfo)。默认回退 liveStore.businessInfoToggle */ businessInfo?: FeatureItemOptions [key: string]: FeatureItemOptions | undefined } /** 商品橱窗可见性 */ goods?: { /** 橱窗「购物车」入口(emit goods.cart.open)。默认 true */ 'cart.open'?: FeatureItemOptions /** 列表卡「加入购物车」按钮(emit goods.cart.add,非积分商品)。默认 true */ 'cart.add'?: FeatureItemOptions /** 橱窗「订单」入口(emit goods.order.open)。默认 true */ 'order.open'?: FeatureItemOptions [key: string]: FeatureItemOptions | undefined } /** 「更多」面板入口可见性 */ more?: { /** 清晰度入口 + 底栏更多下标(emit more.quality)。默认 true;仍受多档流 + DefinitionPlugin 门控 */ quality?: FeatureItemOptions /** 字体大小入口(emit more.fontSize)。默认 true */ fontSize?: FeatureItemOptions /** 清屏入口(emit more.cleanScreen)。默认 true */ cleanScreen?: FeatureItemOptions /** 我的红包入口(emit more.redPacket)。默认 true */ redPacket?: FeatureItemOptions /** 我的积分入口(emit more.score)。默认 true */ score?: FeatureItemOptions /** 我的券码入口(emit more.coupon)。默认 true */ coupon?: FeatureItemOptions /** 我的订单入口(emit more.order)。默认 true */ order?: FeatureItemOptions /** 意见/反馈入口(emit more.feedback)。默认 true */ feedback?: FeatureItemOptions [key: string]: FeatureItemOptions | undefined } [namespace: string]: unknown}
interface FeatureItemOptions { /** UI 可见性。false 隐藏入口;未设 → 回退下一优先级层 */ enabled?: boolean /** 预留排序位(本次不消费) */ order?: number /** 前向兼容扩展槽 */ [key: string]: unknown}
/** 可配置业务特性键 = 该功能激活时 emit 的精确事件键(C-2) */type FeatureKey = | 'topbar.userCenter' | 'topbar.businessInfo' | 'goods.cart.open' | 'goods.cart.add' | 'goods.order.open' | 'more.quality' | 'more.fontSize' | 'more.cleanScreen' | 'more.redPacket' | 'more.score' | 'more.coupon' | 'more.order' | 'more.feedback'可见性优先级:运行时(
setFeature/updateFeatures)> 构造期config.features> 服务端兜底/默认。
// 挂载 Svelte UI 到 DOM 容器sdk.mount(target: string | HTMLElement): Promise<void>
// 销毁 SDK,卸载 UI,清理所有资源sdk.destroy(): void
// 事件系统(由 SDK_EVENTS 类型化)sdk.on(event, handler): thissdk.off(event, handler): thissdk.once(event, handler): this
// 当前校准后的服务端 epoch 毫秒;未获得有效服务端锚点时返回 undefinedsdk.getServerTime(): number | undefined
// 本机真实媒体观看快照;服务端任务累计值不会覆盖它sdk.getWatchProgress(): Readonly<WatchProgressSnapshot>
// 宿主手动注入房间配置时,serverTime 会重校时同一实例 ServerClocksdk.stores.live?.setRoomConfig({ serverTime: 1_700_000_000_000 })
// 插件系统sdk.use(plugin: IPlugin): this
// 业务模块访问sdk.getModule<T>(id: ModuleId): T | undefined
// 应用层入口模块访问(Phase 11,NEEDS-SERVER-VERIFY)sdk.getAppModule(id: AppEntryModuleId): BaseAppModule | undefined
// i18n 文案(公共方法,供宿主页面使用)sdk.t(key: I18nKey): string
// 业务特性配置(FR-FC-06)—— 业务 UI 可见性运行时增量更新,链式返回 this,mount 前可用// 深合并 namespace→leaf 两级;emit features.update {partial}(setFeature 带 key)sdk.updateFeatures(partial: FeatureConfig): thissdk.setFeature(key: FeatureKey, options: FeatureItemOptions): this // key = 对应事件键// 当前特性配置快照(static+runtime 合并,不含服务端兜底)sdk.getFeatures(): Readonly<FeatureConfig>
// 轻量 toast(小程序 wx.showToast 风格,独立于 svelte-sonner 的 sdk.toast)// 全局单例:再次调用仅替换 content 并重置计时(不堆叠)。链式返回 thissdk.showToast(opts: { content: string; duration?: number; mask?: boolean }): this// duration 默认 5000ms;<=0 不自动关闭。mask=true 渲染透明蒙层拦截底层点击(默认 false 穿透)// 最多两行 / 最大宽 90% / 容器内顶层(仅覆盖 SDK 容器,z=100000)。内部核心层亦可 this.showToast() 调用sdk.hideToast(): thisgetServerTime() 以实例为作用域。首次 mount() 认证完成后由 config/detail.serverTime 播种,运行中由 LAPS serverTime 与 ws.room_config.serverTime 持续重校时;房间营销 HTTP 顶层 timestamp 可在未锚定时补充。秒/毫秒自动判位,锚定后只按 performance.now() 的单调增量推进,因此修改设备日期或时区不会改变结果;重复或乱序响应也不会让时间倒退。尚未同步时返回 undefined,调用方应等待 ready/实时帧或隐藏依赖服务端时间的 UI,不要回退 Date.now()。
const serverNow = sdk.getServerTime()if (serverNow !== undefined) { const remainMs = Math.max(0, deadlineMs - serverNow)}事件(SDK_EVENTS)
Section titled “事件(SDK_EVENTS)”| 事件名 | Payload | 说明 |
|---|---|---|
ready |
undefined |
SDK 初始化完成 |
destroy |
undefined |
SDK 销毁 |
error |
{ code: number, message: string } |
全局错误 |
| 事件名 | Payload | 说明 |
|---|---|---|
live.status |
LiveStatus |
直播状态变更 |
live.orientation |
'portrait' | 'landscape' |
布局方向确定(取自 extendSet.screenType + config 覆盖) |
LiveStatus 枚举:
| 值 | 服务端状态码 | 说明 |
|---|---|---|
'not_started' |
102 | 预告 |
'ongoing' |
101 | 直播中 |
'ended' |
103 | 已结束 |
'banned' |
104 | 禁播 |
'paused' |
105 | 暂停 |
'error' |
106 | 异常 |
'expired' |
107 | 已过期 |
'playback' |
108 | 回放(VOD) |
| 事件名 | Payload | 说明 |
|---|---|---|
player.ready |
undefined |
TCPlayer 就绪 |
player.playing |
undefined |
开始播放 |
player.pause |
undefined |
暂停 |
player.error |
{ code: number, message: string } |
播放错误 |
player.duration |
{ duration: number } |
当前视频/全局回放轴总时长(秒) |
player.timeupdate |
{ currentTime: number } |
当前播放位置(TCPlayer timeupdate,节流 250ms) |
player.quality.changed |
{ code: string, name: string } |
兼容事件:目标档位状态改变,不保证目标流已出帧 |
player.quality.switching |
{ switchId, fromCode, toCode, name, reason } |
切流事务开始;当前源继续播放,目标源开始隐藏预加载 |
player.quality.switched |
{ switchId, code, name, reason } |
目标 generation 已取得真实进度并完成画面槽位交换 |
player.quality.switch-failed |
{ switchId, fromCode, toCode, name, reason, recovery, error } |
切流失败;recovery='rolled-back'|'next-lower'|'none' |
player.quality.switch-cancelled |
{ switchId, fromCode, toCode, name, reason, cause, supersededBySwitchId? } |
切流取消;cause='superseded'|'destroy'|'terminal' |
reason 为 'manual'|'downgrade'|'upgrade'|'decode-error'。changed 是兼容的“目标改变”通知,switched 才是“目标流真实出帧”通知;新接入方应按同一 switchId 配对 switching 与三个终态事件。
| 事件名 | Payload | 说明 |
|---|---|---|
chat.message |
ChatMessage |
新聊天消息 |
chat.pinned |
ChatMessage | null |
置顶消息变更 |
chat.mute |
boolean | { muted: boolean } |
全员禁言开关 |
ChatMessage.type:'text' \| 'system' \| 'pinned' \| 'enter' \| 'welcome'(welcome= 进场欢迎语,仅本端可见)。
认证 / IM
Section titled “认证 / IM”IM 错误从全局
error通道分流到im.*事件族,集成方可精细处理 IM 特定错误,与im.reconnected生命周期粒度对齐。
| 事件名 | Payload | 说明 |
|---|---|---|
auth.success |
{ streamUrl: string, qualityList: QualityOption[] } |
auth API 成功,返回预设流地址与归一化清晰度列表 |
auth.kicked |
{ code: number, reason: 'multi_device' | 'multi_account', tip: string } |
单点登录冲突:当前账号已在其他设备/端登录(TIM KICKED_OUT 3002/3003)。触发后 SDK 会展示终态 Modal 并销毁实例,需刷新重新进入 |
im.ready |
undefined |
腾讯 IM SDK 就绪 |
im.reconnected |
undefined |
IM 网络重连成功 |
im.user_banned |
{ userId: string, reason?: string } |
用户被禁言 |
im.user_kicked |
{ userId: string, reason?: string } |
用户被踢出群 |
im.error |
{ code: number, message: string } |
IM 致命错误(如被踢下线) |
im.net_error |
{ code: number, message: string } |
IM 网络断开(与 im.reconnected 成对) |
WebSocket
Section titled “WebSocket”| 事件名 | Payload | 说明 |
|---|---|---|
ws.connected |
undefined |
WS 认证成功,连接就绪 |
ws.disconnected |
{ reason?: string } |
连接断开 |
ws.message |
WSMessage |
原始 WS 帧(调试用) |
ws.error |
{ code: number, message: string } |
WS 错误 |
ws.auth_failed |
{ code: number, message: string } |
认证失败(重试耗尽) |
ws.room_status |
{ status: LiveStatus } |
5000 ROOM_STATUS_NOTIFY 解析结果 |
ws.room_config |
Record<string, unknown> |
5010 ROOM_CONFIG_NOTIFY 原始 payload(Phase 11 扇出 subfield) |
ws.room_activity |
RoomActivityOp(判别联合:coupon_create / lucky_create / checkin_start / checkin_end / display / finish / result) |
6000/6020 房间营销统一事件族(签到/福袋/券);6020 仅本人 agentId 命中。详见 SPEC-ROOM-MARKETING.md §4 |
ws.server_time |
number(服务端毫秒) |
服务端时钟锚定(roomMarketingStore.setServerTime) |
ws.reconnect |
{ attempts: number } |
重连 → roomMarketing entry resync |
互动工具抽屉(内部 UI,非公开 API):FloatBar「更多」打开 FloatMoreDrawer 并调用 SDK 内部 _refreshInteractiveTools() → RoomMarketingEntry.refreshInteractiveTools() → HTTP 79952 + store.reconcilePrime(不遍历 autoOpenIds)。prime/resync/refresh 共用 commitGeneration 门控(last-request-wins)。Store 派生 interactiveItems;refreshing 仅 UI 指示。详情层 z-index:1200 覆盖抽屉。
| ws.commentary | Record<string, unknown> | 解说推送(NEEDS-SERVER-VERIFY)|
| ws.goods_config | Record<string, unknown> | 商品配置更新推送(NEEDS-SERVER-VERIFY)|
| 事件名 | Payload | 说明 |
|---|---|---|
interact.like |
{ count: number, delta: number } |
点赞 |
interact.gift |
GiftPayload |
礼物 |
interact.cart |
undefined |
底部栏购物车点击 |
interact.share |
undefined |
底部栏分享点击 |
interact.more |
{ action?: string } |
底部栏更多点击 |
用户 / 房间
Section titled “用户 / 房间”| 事件名 | Payload | 说明 |
|---|---|---|
user.join |
UserProfile |
用户进入直播间 |
room.viewerCount |
{ count: number } |
观看人数更新 |
| 事件名 | Payload | 说明 |
|---|---|---|
goods.show |
GoodsItem |
展示商品卡片 |
goods.hide |
{ goodsId: string } |
隐藏商品卡片 |
goods.list |
GoodsCardItem[] |
HTTP 首屏全量列表就绪;项含可选 marketingActivities(营销活动摘要 actId/actType/actName,满减/满赠本地归一用) |
goods.buy |
{ numIid, scoreGoods? } |
点购买/兑换按钮(列表卡 + 浮窗卡统一),host 接管下单 |
goods.card.click |
{ numIid, name?, scoreGoods? } |
商品卡片整体点击(列表卡 + 浮窗卡空白区,按钮已 stopPropagation) |
goods.promotion.click |
{ numIid, scoreGoods? } |
兑换券胶囊点击通知;autoOpenExchangeCoupon=false 时 host 可自开,true 时 SDK 已自开、host 不得重复打开 |
goods.cart.add |
{ numIid, scoreGoods? } |
列表卡「加入购物车」按钮点击(非积分商品、未售罄) |
goods.cart.open |
undefined |
商品橱窗标题栏「购物车」入口点击(区别于底栏 interact.cart) |
goods.order.open |
undefined |
商品橱窗标题栏「订单」入口点击 |
goods.open / goods.close |
undefined |
商品面板开 / 关 |
goods.marketing.open |
{ numIid } |
营销弹框打开 |
goods.marketing.show |
MarketingDetail |
营销详情就绪(sections 可含 fullReduction 满减/满赠摘要,本地归一不发请求) |
goods.marketing.error |
{ numIid, message? } |
营销拉取失败(可重试) |
goods.marketing.close |
undefined |
营销弹框关闭 |
⚠️ 破坏性:浮窗卡(HotSellCard)购买按钮由
interact.cart统一为goods.buy,旧宿主需迁移。
兑换券入口/弹框按
scoreGoodsInfo.useCouponType分流(详见「数据模型」):1=商品券(分页「可用兑换券」+ 需要/已有)、2=普通券+万能券(内联「商品所需兑换券」+ 可显万能券)、0/空=不使用(无胶囊/弹窗)。商品不在列表且scoreGoods=true时程序式打开回退 type1。goods.promotion.clickpayload 与autoOpenExchangeCoupon语义不变。
| 事件名 | Payload | 说明 |
|---|---|---|
ws.room_activity |
RoomActivityOp(判别联合) |
当前实现:6000/6020 房间营销统一事件族。券=福袋 type=1。详见 SPEC-ROOM-MARKETING.md §4 |
⚠️ 已废止事件(勿监听):
activity.change/activity.result/activity.participate(旧 ActivityPanel 体系,已删组件);coupon.show/coupon.open/coupon.close(旧 CouponStack 徽标链路);ws.coupon_dispatch(旧 6000/6020 回落通道)。均已归ws.room_activity+roomMarketingStore。
左上角营销互动浮窗(image-text-float)
Section titled “左上角营销互动浮窗(image-text-float)”| 事件名 | Payload | 说明 |
|---|---|---|
imagetext.change |
ImageTextChange |
LAPS check.in.* / lucky.packet.* 映射为浮窗数据变更;内部 ImageTextEntry 消费 |
imagetext.entry.click |
{ type, current, stableKey } |
用户点击浮窗图标;CHECKIN 路由到 checkin.show,其余面板留后续 |
imagetext.icon.close |
{ stableKey, type } |
用户关闭单个浮窗图标 |
imagetext.more.click |
undefined |
用户打开「更多」抽屉 |
checkin.show / checkin.close |
{ currentId } / undefined |
签到面板打开 / 关闭 |
checkin.detail |
CheckInDetail |
签到详情拉取成功 |
checkin.submit_result |
{ success: boolean, message: string } |
签到提交结果 |
checkin.kickout |
{ banTips: string } |
截止未签到且活动要求禁止观看 |
| 事件名 | Payload | 说明 |
|---|---|---|
features.update |
{ partial: FeatureConfig, key?: FeatureKey } |
updateFeatures/setFeature 更新业务特性配置后 emit(setFeature 带 key) |
more.quality |
undefined |
「更多」面板清晰度入口点击(先于打开清晰度 ActionSheet) |
more.fontSize |
undefined |
「更多」面板字体大小入口点击(先于打开字号 ActionSheet) |
more.cleanScreen |
undefined |
「更多」面板清屏入口点击(先于进入清屏) |
more.redPacket |
undefined |
「更多」面板「我的红包」入口点击(先于 openRedPacket()) |
more.score |
undefined |
「更多」面板「我的积分」入口点击(宿主导航积分中心) |
more.coupon |
undefined |
「更多」面板「我的券码」入口点击(宿主导航券码列表) |
more.order |
undefined |
「更多」面板「我的订单」入口点击(宿主导航订单列表) |
more.feedback |
undefined |
「更多」面板「意见/反馈」入口点击(先于转发 topbar.feedback) |
| 事件名 | Payload | 说明 |
|---|---|---|
watch.progress |
WatchProgressSnapshot |
双门控观看快照;含 total/seconds/state/watchKind/byKind/sequence/reason |
watch.state.changed |
{ previous, current, snapshot } |
观看状态变化 |
watch.report.calibration.started/ack/stale/failed/degraded |
WatchReportEventMeta |
ordinary/Range 校准与一致性结果 |
watch.report.queued/attempt/retry/ack/rejected/parked |
WatchReportEventMeta |
可靠上报漏斗;只含脱敏元数据 |
watch.report.exit.queued/kickout.queued/finalize.error/final.abandoned |
WatchReportEventMeta |
退出/踢出最终对账漏斗 |
duration.change |
{ totalMs: number, seconds: number } |
废弃;watch.progress 的 5s 兼容映射 |
duration.report |
{ seconds: number, success: boolean } |
废弃;仅 ordinary 逻辑 ACK/终态失败 |
应用模块生命周期
Section titled “应用模块生命周期”| 事件名 | Payload | 说明 |
|---|---|---|
app.module.init |
{ moduleId: string } |
应用模块初始化 |
app.module.destroy |
{ moduleId: string } |
应用模块销毁 |
Phase 11 入口模块事件(NEEDS-SERVER-VERIFY)
Section titled “Phase 11 入口模块事件(NEEDS-SERVER-VERIFY)”以下事件 payload schema 由 5010 ROOM_CONFIG_NOTIFY subfield 推断,未经真实 LAPS 端点验证。
| 事件名 | Payload | 说明 |
|---|---|---|
lottery.start |
Record<string, unknown> |
抽奖开始推送 |
lottery.open |
undefined |
打开抽奖面板 |
lottery.close |
undefined |
关闭抽奖面板 |
task.new |
Record<string, unknown> |
新任务推送 |
task.open |
undefined |
打开任务面板 |
task.close |
undefined |
关闭任务面板 |
task.state.changed |
{ state: TaskRewardEntryState } |
任务、进度、领奖中和当前奖励的冻结快照 |
task.reward.present |
TaskRewardPresentation |
新完成奖励串行呈现;宿主结束后调用 acknowledgeReward(key) |
task.reward.closed |
{ key, reason } |
奖励关闭,reason 为 host_ack/destroy/skipped |
task.receive.result |
{ taskId, taskGenerationKey, success, ignoredAsStaleGeneration, error? } |
普通任务手动领奖结果 |
goods.config |
Record<string, unknown> |
商品列表配置推送 |
goods.open |
undefined |
打开商品面板 |
goods.close |
undefined |
关闭商品面板 |
commentary.push |
Record<string, unknown> |
直播解说推送 |
commentary.open |
undefined |
打开解说面板 |
commentary.close |
undefined |
关闭解说面板 |
TaskRewardEntry 已从主包导出。宿主在 ready 后用 sdk.getAppModule<TaskRewardEntry>('taskReward') 获取,方法包括:refresh()、getState()、subscribe(listener)、receiveOrdinaryTask({taskId,taskGenerationKey})、acknowledgeReward(key)、open()、close()。状态使用 TaskRewardEntryState,任务视图为 TaskView | RangeTaskView 判别联合;返回值和事件 payload 均为深只读隔离快照。
- 普通观看条件读取
watchCondition.watchHour/watchMinutes/checkInCount;互动条件读取interactiveCondition.inviteType/inviteNum。 - 普通奖励读取
awardSet与awardReceiveType,手动领奖仅允许award.receiveMode==='manual'、已完成且仍在有效期内的 active generation。 - Range 进度由
livingWatchSeconds + historyWatchSeconds + otherWatchSeconds组成;未返回领取字段时rewardState='unknown',不会默认成未领取;receiveOrdinaryTask()不支持 Range。 progressByChannel.ordinary/range.effectiveSeconds是跨设备服务端锚点叠加本机会话增量后的展示/阈值口径;不要用getWatchProgress().seconds替代服务端任务累计值。
完整接入示例见 GUIDE-APP-LAYER-MARKETING.md。
interface ChatMessage { id: string userId: string userName: string userAvatar?: string content: string timestamp: number type: 'text' | 'system' | 'pinned' /** 来自 IM cloudCustomData.levelId,收端经 memberLevels 匹配 */ levelId?: string | number levelName?: string levelColor?: string levelIcon?: string}
interface GiftPayload { giftId: string giftName: string giftIcon?: string userId: string userName: string count: number}
interface UserProfile { userId: string userName: string avatar?: string level?: number}
interface GoodsItem { id: string name: string price: number image?: string url?: string}
// 商品关联营销活动摘要(goodsCard/get 商品项 marketingActivities 内联;仅摘要三字段,// 无档位/赠品明细;满减/满赠本地归一为 fullReduction section,不发请求)interface MarketingActivity { actId: number // 活动 ID actType: number // 活动类型码(0=满减, 1=满赠/赠送商品;抓包确认 2026-07-09) actName: string // 摘要文案,如「满100减20」「满2件赠1」}
// fullReduction section 数据(v2 摘要级);档位/赠品明细扩展位待后端补字段interface FullReductionSectionData { activities: MarketingActivity[]}
// 商品列表项(goods.list payload 项)。仅列营销相关字段,其余基础字段同 GoodsItem。interface GoodsCardItem { numIid: string scoreGoods?: boolean // 积分商品标识 /** 积分商品详情(scoreGoods=true 时存在) */ scoreGoodsInfo?: { score?: number price?: number couponNum?: number couponName?: string | null couponId?: number | null /** 列表态:type1=null(用 couponNum + 分页接口);type2 内联(实测项 {couponNum,couponId,couponName}) */ couponList?: Array<{ couponNum?: number couponName?: string couponId?: number code?: string consumeNum?: number count?: number name?: string // 对齐 live-app Coupon.name,couponName 缺失时作标题兜底 }> | null exchangeNum?: number limitNum?: number /** * 兑换券类型枚举(✅ 已确认 2026-07-14): * 0/空 = 不使用(不渲染兑换券胶囊、不打开兑换券弹窗) * 1 = 商品券(couponNum + 分页接口,列表态 couponList=null) * 2 = 普通券+万能券(列表态内联 couponList) */ useCouponType?: number } marketingActivities?: MarketingActivity[] /** 是否支持万能券(商品项顶层;仅 useCouponType=2 消费;缺失→false) */ supportUniversalCoupon?: boolean}
// 兑换券(积分商品)section 数据;couponType 决定渲染分支interface ExchangeCouponSectionData { coupons: Array<{ couponNum?: number couponName?: string name?: string // 标题兜底(对齐 live-app Coupon.name) couponId?: number code?: string consumeNum?: number // 计数回退链 couponNum → consumeNum → count count?: number }> /** 兑换券类型:1=商品券(分页) / 2=普通券+万能券(内联);marketingApi 必填,驱动渲染分支 */ couponType?: 1 | 2 /** 商品所需兑换券总数(type1=scoreGoodsInfo.couponNum / type2=sum(couponList)),非券种数 */ count: number /** 用户已持有数(mySelfCouponNum;仅 type1) */ mine?: number /** 是否展示万能券提示(仅 type2) */ isSupportUniversalCoupon?: boolean /** 分页(仅 type1) */ hasMore?: boolean pageNo?: number loadingMore?: boolean loadMoreError?: string | null}通过 sdk.getModule<T>(id) 访问:
// Player 模块const player = sdk.getModule<Player>('player')player.play()player.pause()player.setVolume(0.8) // 0–1player.attachElement(videoEl)
// IM 模块const im = sdk.getModule<IM>('im')await im.sendTextMessage('Hello!')const history = await im.getHistoryMessages(20)
// Interact 模块const interact = sdk.getModule<Interact>('interact')await interact.sendLike(3)
// User 模块const user = sdk.getModule<User>('user')await user?.setProfile({ nickName: '昵称', avatar: 'https://...' }) // 部分字段可省略await user?.refreshUserSig(newSig)// 只读:user.userId · user.userName · user.avatar · user.levelId(来自 ve/token)应用层入口模块(Phase 11,NEEDS-SERVER-VERIFY)
Section titled “应用层入口模块(Phase 11,NEEDS-SERVER-VERIFY)”// 房间营销(签到 / 福袋 / 券)——命令式参与await sdk.claimCoupon('packetId') // 实时券领取(上行 6010 + 等 6020 / 8s 乐观回退)sdk.joinCheckIn('checkInId') // 签到(省略 id → 取当前 active)await sdk.joinLuckyPacket('packetId') // 福袋参与(等开奖结果)
// 其它入口模块手动触发开/关const lottery = sdk.getAppModule('lottery')lottery?.open()DanmuPlugin
Section titled “DanmuPlugin”import { DanmuPlugin } from 'we-livesdk/plugins'
sdk.use(new DanmuPlugin({ speed: 8, // 滚动时长(秒) fontSize: 14, opacity: 0.85, maxLines: 3,}))WatermarkPlugin
Section titled “WatermarkPlugin”import { WatermarkPlugin } from 'we-livesdk/plugins'
sdk.use(new WatermarkPlugin({ text: 'Username', position: 'top-right', // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' opacity: 0.4,}))DefinitionPlugin
Section titled “DefinitionPlugin”清晰度自适应插件(opt-in,须显式注册)。开启后:起播按 defaultQuality 选档、弱网/解码丢帧自动降档、手动切档锁定;「更多」面板清晰度入口需此插件存在才显示(门控 hasMultiQuality && getPlugin('definition'))。
// ESM 主路径import { LiveSDK, DefinitionPlugin } from 'we-livesdk'const sdk = new LiveSDK({ activityId, token, merchantId })sdk.use(new DefinitionPlugin({ defaultQuality: 'auto' }))
// 或子路径import { DefinitionPlugin } from 'we-livesdk/plugins'sdk.use(new DefinitionPlugin())
// UMD(裸 HTML)全局sdk.use(new LiveSDK.DefinitionPlugin())| 选项 | 默认 | 说明 |
|---|---|---|
defaultQuality |
'preset' |
'preset'(服务端预设源) | 'auto'(按设备分级) | 'highest' | 'lowest' | 具体 code |
auto |
true |
是否启用自动自适应(弱网/解码降级;false 仅手动切档) |
allowUpgrade |
false |
网络恢复后自动回升一档 |
只读 manualMode |
— | 手动锁定态(代理 playerStore.manualMode) |
方法 setManualMode(on) |
— | true 锁定禁用软性降级 / false 恢复自适应 |
manualOverrideMs |
— | @deprecated(手动锁定改为永久,不再用时间戳) |
stallWindowMs |
10000 |
单个冻结 episode 的最长观察时间(ms) |
stallThreshold |
3 |
可靠帧指标所需连续冻结窗;帧指标不可靠时使用 max(stallThreshold+1, 4) |
cooldownMs |
15000 |
自动降档软冷却(ms) |
dropFrameSampleMs |
5000 |
丢帧采样窗(ms);须连续 2 个有效窗命中 |
dropFrameRatio |
0.1 |
每个有效窗的丢帧率阈值;窗内新增总帧不足 30 不计 |
decodeErrorCooldownMs |
5000 |
解码错误硬路径冷却(ms) |
decodeErrorThreshold |
2 |
同档连续解码错误触发阈值 |
默认不变量:defaultQuality='preset'、allowUpgrade=false;手动请求被 Player 接纳即设置 playerStore.manualMode=true,直到选择自动或调用 setManualMode(false)。
运行约束:正常 101 进房会在 auth.success 前原子确定最终档位/URL,事件发出后只起播一次;108→101 会先退出 VOD,再真实恢复仍存在的手动档,档位已消失时使用服务端预设但保留锁定。运行时切流不立即替换当前源:目标流先在隐藏、静音的 standby 播放器中预加载,只有目标 generation 收到 loadstart、playing 且 currentTime/totalVideoFrames 相对基线增长后,才交换画面并发 player.quality.switched。双槽位存在时,平台、音量、静音及微信桥接开声状态均不得触发 active 原地 switchStream();预加载期间开声只更新声音真值,不销毁 standby。交换时不修改旧 active 的 muted 状态,旧 active 停止后才把 Player 缓存的音量/静音应用到新 active。预加载失败/超时只释放 standby,当前源保持播放;非 superseded 取消还会把档位、URL 与 state sink 恢复到 confirmed 真值。公开 pause() 会同步暂停 active/standby 并挂起事务,play() 后恢复。候选和回滚中间错误不公开 player.error。
健康判定约束:重复 waiting/stalled 可即时复采样,但不足 1 秒不得增加冻结窗口;观察到真实推进即结束本 episode。页面后台、暂停、VOD、初始化、切流事务、错误退避或直播终态期间不执行丢帧降档;手动锁定或最低档确认冻结时保持 stall-owner lease,并由 Player 执行同源恢复。
class AuthError extends Error { code: number // HTTP 状态码或业务错误码 message: string}auth API 调用失败时抛出 AuthError,可在 mount() 的 catch 中捕获。
import { LiveSDK } from 'we-livesdk' // 主入口const { LiveSDK: EmbeddedLiveSDK } = await import('we-livesdk/legacy') // 宿主安全 ESM;单文件、无 CSS 副作用import { DefinitionPlugin } from 'we-livesdk' // 清晰度插件(主路径)import { LiveRoom } from 'we-livesdk/components' // Svelte 组件import { createChatStore } from 'we-livesdk/stores' // Store 工厂import { DanmuPlugin } from 'we-livesdk/plugins' // 插件import type { SDKConfig } from 'we-livesdk/types' // 仅类型import 'we-livesdk/styles' // Tailwind CSSimport 'we-livesdk/embed.css' // 完整嵌入样式:Tailwind + Svelte/TCPlayer,命名空间隔离we-livesdk/embed.css 关闭 preflight,包含 Tailwind utilities 与构建期提取的 Svelte scoped/TCPlayer 组件样式;所有规则统一限制在 .livesdk-container 下。pnpm build / pnpm package 会生成标准 Svelte package、dist/embed.css、dist-umd/livesdk.legacy.js 并执行 publint;clean checkout 不依赖残留构建产物。pnpm build:umd 额外生成 UMD/IIFE/ESM 演示产物,并同步重建 legacy 入口。
完整使用示例
Section titled “完整使用示例”import { LiveSDK, SDK_EVENTS } from 'we-livesdk'import { DanmuPlugin } from 'we-livesdk/plugins'import 'we-livesdk/styles'
const sdk = new LiveSDK({ activityId: 'room-001', token: 'your-jwt-token', merchantId: 'your-merchant-id', locale: 'zh-CN',
tim: { sdkAppId: 1400000000, userId: 'viewer-123', userSig: await fetchUserSig('viewer-123'), groupId: 'live-room-001', },
ws: { url: 'wss://your-laps-host/ws', },
layout: { orientation: 'auto', // 读 API body.extendSet.screenType },
options: { logLevel: 'info', theme: { primaryColor: '#ff6b35', radius: '8px', }, },})
sdk.use(new DanmuPlugin({ speed: 8 }))
sdk.on(SDK_EVENTS.READY, () => { console.log('SDK ready')})
sdk.on(SDK_EVENTS.LIVE_ORIENTATION, (orientation) => { console.log('布局方向:', orientation) // 'portrait' | 'landscape'})
sdk.on(SDK_EVENTS.CHAT_MESSAGE, (msg) => { console.log(`${msg.userName}: ${msg.content}`)})
sdk.on(SDK_EVENTS.DURATION_CHANGE, ({ seconds }) => { console.log('已观看', seconds, '秒')})
sdk.on(SDK_EVENTS.PLAYER_ERROR, ({ code, message }) => { console.error(`播放错误 ${code}: ${message}`)})
// WS 连接状态sdk.on(SDK_EVENTS.WS_CONNECTED, () => { console.log('WebSocket 已连接')})
sdk.on(SDK_EVENTS.WS_ROOM_STATUS, ({ status }) => { console.log('直播状态(WS推送):', status)})
await sdk.mount('#live-container')
// 清理window.addEventListener('beforeunload', () => sdk.destroy())TelemetryConfig v3
Section titled “TelemetryConfig v3”telemetry 默认关闭,且与 options.logLevel 独立。公开字段为 enabled/profile/endpoint/transport/identityMode/experiments/diagnostic/sampleRate/batchSize/flushIntervalMs/maxQueue/maxBytes/appContext。endpoint 与 transport 互斥:开发态冲突抛配置错误,生产态关闭遥测并告警,不中断 SDK。ctx.userKey 始终上报去首尾空白后的明文用户标识,ctx.deviceKey 继续使用 SHA-256 截断哈希;空用户不产生 userKey。identityMode 与旧 appContext.plaintextIdentity 仅为兼容保留字段,不再改变身份输出。生产采样率无有效 diagnostic 时最高 0.1;只有 diagnostic.userKey 精确匹配明文 ctx.userKey 且 expiresAt 在未来 30 分钟内才提升到 1。
根入口与 we-livesdk/types 均导出 TelemetryConfig/TelemetryTransport/TelemetryBatchV3/TelemetryContext/TelemetryProfile/TransportResult/SendMeta/ExitMeta 类型,第三方 Transport 无需引用内部路径。
批次为 {v:3,bid,sid,bseq,t0,ctx,events,sidecars?,stats?},事件元组为 [code,seq,dt,...numberPayload]。宿主 appContext 的页面/应用/构建字段会进入受控上下文,用户标识以明文 userKey 上报,设备标识保持哈希。内置 HTTP 用 text/plain;charset=UTF-8,单批 ≤48KiB、队列 ≤100 条/64KiB。
完整解析契约见 TELEMETRY-V3-REFERENCE.md:14/14 个事件均登记精确 payload 位置、类型、单位、枚举/位图,顶层 batch、ctx/stats/sidecars 也逐字段登记。真源 packages/live-observability-contract/manifest.json 同时生成 JSON Schema、本地枚举和参考表;严格解析器会拒绝未知 code、错误 arity、未知枚举/位图及未登记字段。服务端导出直接 batch、batch 数组或逐行 JSON 后,可执行 node packages/live-observability-contract/scripts/decode.mjs exported-log.json。本契约只验收客户端请求体完整可解析,不要求证明服务端已经接收或落库。