# 神岛赛事直播SDK **Repository Path**: box3lab/box-live-sdk ## Basic Information - **Project Name**: 神岛赛事直播SDK - **Description**: 支持赛程定位、队伍/玩家查询、KDA/综合分统计、排行榜、分位、以及事件订阅。 - **Primary Language**: TypeScript - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-17 - **Last Updated**: 2025-08-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 神岛赛事直播工具 支持赛程定位、队伍/玩家查询、赛程进度/倒计时、队员信息上传,以及事件订阅。 注意:本 SDK 不提供 KDA/分数/排行榜/MVP 等统计计算;`LiveEvent` 仅管理“配置的比赛信息”,`LiveUpdater` 负责把你的实时数据(比分、玩家扩展等)上报到平台。 ## 简介 本 SDK 面向地图/工具开发者,提供只读赛事数据查询(`LiveEvent`)与实时数据上报(`LiveUpdater`),覆盖赛程/队伍/玩家查询、时间对齐、事件订阅,以及高光/弹幕和比分上报的全流程。 最低兼容版本:`@dao3fun/live-sdk@^0.2.0` ## 安装 ```bash npm install @dao3fun/live-sdk ``` ## 快速开始 ```ts import { LiveEvent } from '@dao3fun/live-sdk'; async function main() { const live = LiveEvent.getInstance(); // 选择数据源:测试 or 生产(异步,内部会立即拉一次) await live.useTestData(true); // 测试源(完整版,不随直播变动,便于联调) // await live.useTestData(false); // 生产源(上线前请切回) // 可选:固定“当前时间”做回放/测试,秒级时间戳,在两个队伍比赛时内有效 live.setNowSecOverride(1755498600); // live.useRealTime(); // 取消覆盖,恢复实时 // 订阅数据更新 const off = live.onUpdate((payload) => { console.log('Updated:', payload.title); }); // 立即拉一次(可选;useTestData 已经会拉一次) await live.refreshNow(); // 读取当前比赛与地图、赛程进度与倒计时 const curMatch = live.getCurrentMatch(); const curMap = live.getCurrentMatchMap(); const progress = live.getCurrentMatchProgress(); const countdown = live.getCountdownToNextMatch(); console.log({ curMatch, curMap, progress, countdown }); off(); } main().catch(console.error); ``` ### LiveUpdater 最简上报(当前场次) ```ts import { LiveEvent, LiveUpdater, type LiveUpdatePlayer, } from '@dao3fun/live-sdk'; // 使用 LiveUpdater 上报 const updater = new LiveUpdater({ key: 'demo_player_extras' }); async function pushNow() { // 获取 SDK 实例 const live = LiveEvent.getInstance(); await live.useTestData(true); // 示例:测试源 // 获取当前进行中的比赛 const cur = live.getCurrentMatch(); if (!cur) return; // 覆盖“当前场次”的玩家扩展字段(按玩家 id) const extras: Record> = { 101: { score: 12, hp: 88, kda: '2/1/3' }, 102: { score: 7 }, }; // 上报 当前比赛信息 // 他会自动记录对战历史,你只需要传入当前比赛的信息即可。 // 注意,当前比赛和队员的信息一定要匹配,否则无效。 const ok = await updater.pushFromMatch(cur, { teamA: 10, teamB: 9 }, extras); // 返回结果 console.log('push ok?', ok); } // 执行 pushNow().catch(console.error); ``` ## 数据源:生产/测试切换 - `await live.useTestData(enable: boolean): Promise` - **功能**:切换数据源为测试或生产环境,并立即异步拉取一次最新数据。 - **参数**:`enable: boolean` - `true` 表示切换到测试数据源,`false` 表示切换到生产数据源。 - **用途**:方便在开发/联调时使用固定的测试数据,上线前再切换回实时生产数据。 - `live.isUsingTestData(): boolean` - **功能**:检查当前是否正在使用测试数据源。 - `await live.useCustomUrl(url: string): Promise` - **功能**: 切换到自定义的数据源 URL,并立即异步拉取一次最新数据。 - **参数**: `url: string` - 自定义数据源的 URL。 - **用途**: 用于连接到非官方或代理的数据接口。 - 语义: - 测试源:完整静态数据,便于测试/联调(`DEFAULT_LIVE_URL_TEST`) - 生产源:线上实时数据(`DEFAULT_LIVE_URL`,上线前务必切回) - 示例: ```ts const live = LiveEvent.getInstance(); // 测试环境联调 await live.useTestData(true); console.log('Using test?', live.isUsingTestData()); // true // 发布到正式前 await live.useTestData(false); ``` ## 刷新与轮询 - `refreshNow(): Promise` - **功能**:立即异步拉取一次最新数据。 - **用途**:在需要确保获取到最新信息时(如执行统计前)手动调用。如果拉取失败,会触发 `onError` 事件,并且外层调用可以 `catch` 捕获错误。 - `stop(): void` - **功能**:停止内部的自动轮询。 - **用途**:在不需要实时更新数据时(如页面不可见或组件销毁时)调用,以节省资源。 - `resume(): void` - **功能**:恢复内部的自动轮询。 - **用途**:在需要时重新开启自动数据更新。 - `setFetcher(fetcher): void` - **功能**:设置自定义的数据请求器。 - **参数**:`fetcher` - 一个函数,接收 `url` 作为参数,并返回一个包含 `json()` 方法的对象,如 `(url) => fetch(url)`。 - **用途**:用于高级定制,例如在请求中添加自定义的鉴权头、日志记录或使用 mock 数据。 - **默认行为**:SDK 默认已开启短周期轮询,自动获取最新数据。 ```ts live.setFetcher(async (url) => { const res = await fetch(url, { headers: { 'x-token': '...' } }); return { json: () => res.json() }; }); ``` ## 全局时间覆盖(用于回放/对齐) - `setNowSecOverride(sec?: number): void` - **功能**:覆盖 SDK 内部使用的“当前时间”,单位为 Unix 时间戳(秒)。 - **参数**:`sec?: number` - 可选的时间戳。如果传入 `undefined` 或不传,则清除覆盖,等同于 `useRealTime()`。 - **用途**:用于测试和回放。通过固定一个时间点,可以稳定地测试依赖于当前时间的功能(如 `getCurrentMatch()`)。 - `useRealTime(): void` - **功能**:取消时间覆盖,让 SDK 恢复使用真实的系统时间。 - **用途**:在测试或回放结束后,调用此方法以返回正常模式。 - **影响范围**:所有接受可选 `nowSec` 参数的方法,在未显式传入该参数时,都会优先使用这里设置的覆盖时间。 ```ts live.setNowSecOverride(1755498600); const cur = live.getCurrentMatch(); // 将基于覆盖时间判断 live.useRealTime(); ``` ## 事件订阅 - `onUpdate(cb): () => void` - **功能**:订阅数据更新事件。当拉取到的数据与上一次相比发生变化时,回调函数 `cb` 会被调用。 - **参数**:`cb: (payload: LivePayload) => void` - 数据更新时的回调函数。 - **返回**:一个函数,调用该函数可以取消此次订阅。 - `waitForNextUpdate(timeoutMs?): Promise` - **功能**:返回一个 Promise,它会在下一次数据更新时 resolve,或在超时后 reject。 - **参数**:`timeoutMs?: number` - 可选的超时时间(毫秒)。 - **用途**:用于需要等待特定更新才能继续执行的场景。 - `onError(cb): () => void` - **功能**:订阅数据拉取失败事件。 - **参数**:`cb: (error: Error) => void` - 发生错误时的回调函数。 - **返回**:一个函数,调用该函数可以取消此次订阅。 - `onPlayerUpdate(playerId, cb): () => void` - **功能**:监听特定玩家在“当前比赛”中的引用变化(是否在场等)。 - **参数**: - `playerId: number` - 要监听的玩家 ID。 - `cb: (playerRef: PlayerRef | undefined) => void` - 当玩家在场状态变化时触发;不在当前比赛则为 `undefined`。 - **返回**:取消订阅函数。 ```ts const offAll = live.onUpdate(console.log); const offErr = live.onError(console.error); const offP = live.onPlayerUpdate(123, (ref) => console.log('P123:', ref)); await live.waitForNextUpdate(10000); // 最多等 10s offAll(); offErr(); offP(); ``` ## 顶层信息读取 - `getType(): 'Game' | 'Live'`:获取赛事类型,例如是普通游戏还是直播活动。 - `getEnv(): string`:获取当前数据源的环境标识。 - `getTitle(): string`:获取赛事的完整标题。 - `getDescription(): string`:获取赛事的描述信息。 - `getRoomId(): number`:获取直播或比赛房间的 ID。 - `getCover(): string`:获取赛事的封面图片 URL。 - `getStartTime(): number` / `getEndTime(): number`:以 Unix 时间戳(秒)格式,获取赛事的开始和结束时间。 - `getStartDate(): Date` / `getEndDate(): Date`:以 JavaScript `Date` 对象格式,获取赛事的开始和结束时间。 - `isLive(nowSec?): boolean`:判断当前是否处于赛事直播时间段内。可以提供一个可选的 `nowSec` 参数来基于特定时间点进行判断。 ## 赛事配置与地图 - `getGameCfg(): GameCfg | undefined`:获取游戏的核心配置信息,如果存在的话。 - `getLogo(): string | undefined`:获取赛事的 Logo 图片 URL。 - `getTeams(): Team[]`:获取在赛事配置中定义的所有参赛队伍的列表。 - `getMatchMaps(): MatchMap[]`:获取本次赛事所有比赛地图的列表。 - `getCommentators(): number[]`:获取解说员的用户 ID 列表。 - `getOperators(): number[]`:获取中控台成员的用户 ID 列表。 - `getBracket(): Bracket | null`:获取晋级图信息,如果不存在则返回 `null`。 ## 赛程/比赛 - `getMatches(): Match[]`:获取原始的比赛列表。 - `getMatchesSortedByStart(): Match[]`:获取按开始时间排序的比赛列表。 - `getFirstMatch()` / `getLastMatch()`:获取第一场和最后一场比赛。 - `getMatchAt(unixSec: number)`:获取在指定时间点(Unix 秒)正在进行的比赛。 - `getCurrentMatch(nowSec?)`:获取当前时间正在进行的比赛。 - `getNextMatch(nowSec?)`:获取当前时间之后的下一场比赛。 - `getCurrentMatchIndex(nowSec?)`:获取当前比赛在赛程中的索引。 - `getMatchMapByIndex(index: number)`:根据赛程索引获取比赛地图。 - `getCurrentMatchMap(nowSec?)`:获取当前比赛的地图信息。 - `getCurrentMatchDurationSec(nowSec?)`:获取当前比赛已经进行的时长(秒)。 - `getTimeToNextMatchSec(nowSec?)`:获取距离下一场比赛开始还有多少秒。 - `getCurrentMatchProgress(nowSec?)`:以 `0-1` 的小数形式,估算当前比赛的进度(基于下一场比赛的开始时间)。 - `getCountdownToNextMatch(nowSec?)`:获取距离下一场比赛开始的倒计时(秒)。 ## 队伍/玩家便捷 - `getTeamById(id)` / `getTeamByName(name)`:通过队伍 ID 或名称查找并返回队伍对象。 - `getTeamPlayers(teamId)` / `getTeamPlayerIds(teamId)`:获取指定队伍的所有队员(`PlayerRef[]`)或队员 ID 列表(`number[]`)。 - `getAllPlayerIdsFromTeams()` / `getAllPlayerRefsFromTeams()` / `getAllPlayers()`:获取所有队伍的所有队员 ID 列表、队员引用对象列表或队员引用对象列表(`getAllPlayers` 是 `getAllPlayerRefsFromTeams` 的别名)。 - `getMatchTeams(match)` / `getMatchPlayers(match)`:获取指定比赛的参赛队伍或所有参赛队员。 - `getCurrentTeams(nowSec?)`:获取当前比赛的两个参赛队伍(按队伍名称匹配 `Team` 对象,可能返回 `undefined`)。 - `getCurrentMatchPlayerRefs(nowSec?)`:获取当前比赛所有在场玩家的 `PlayerRef` 列表。 - `getCurrentMatchTeamsRefs(nowSec?)`:获取当前比赛按阵营(`teamA`, `teamB`)划分的在场玩家 `PlayerRef` 列表。 - `isPlayerInCurrentMatch(playerId, nowSec?)`:判断指定 ID 的玩家当前是否在场比赛。 - `getTeamByPlayerId(playerId)` / `getPlayerTeamId(playerId)`:根据玩家 ID 查找其所在的队伍对象或队伍 ID。 - `isPlayerInTeam(teamId, playerId)`:判断指定玩家是否属于指定队伍。 - `getLineupDiffForTeam(teamId, nowSec?)`:比较指定队伍的配置阵容与当前实际上场阵容的差异。 - `onStageButNotInTeam`: 返回在场但未在队伍配置中的玩家。 - `inTeamButNotOnStage`: 返回在队伍配置中但未上场的玩家。 ## 客户端/视图辅助 - `getScreenType(commentatorsEntity: GamePlayerEntity): 'live_mini' | 'live' | undefined` - **功能**:从解说员实体的 URL 参数中,解析并获取当前客户端的界面类型。 - **参数**:`commentatorsEntity: GamePlayerEntity` - 解说员的玩家实体对象。 - **返回**: - `'live_mini'`: 小窗副屏直播模式。 - `'live'`: 大窗主屏直播模式。 - `undefined`: 未指定或无法识别。 - **用途**:用于根据启动参数,在客户端展现不同的 UI 布局。 - `getPlayerUiHidden(entity: GamePlayerEntity): boolean` - **功能**:从玩家实体的 URL 参数中,判断是否需要隐藏游戏内 UI。 - **参数**:`entity: GamePlayerEntity` - 玩家实体对象。 - **返回**:`true` 表示需要隐藏 UI,`false` 表示正常显示。 - **用途**:用于实现“导演”或“观察者”模式,提供一个干净的无 UI 画面。 ## 玩家引用 - `getPlayerRefById(playerId, nowSec?)`: 根据玩家 ID 获取其在当前比赛中的引用对象(`PlayerRef`)。若玩家不在当前比赛,返回 `undefined`。 ## 历史与即将开始 - `getHistory()` / `getUpcoming()`: 获取原始的“历史活动”和“即将开始”的活动列表。 - `getHistorySortedByStart()` / `getUpcomingSortedByStart()`: 获取按开始时间排序的“历史活动”和“即将开始”的活动列表。 ## 序列化 - `toJSON(): LivePayload`: 返回最新的原始数据负载对象(`LivePayload`),方便进行序列化(如 `JSON.stringify`)或传递。 ## 类型(来自 `server/src/lib/types.ts`) - `PlayerRef`: `{ id: number }` - `Team`: `{ id; name; logo; players: PlayerRef[] }` - `MatchTeam`: `{ name; logo; players }` - `Match`: `{ startTime; teamA: MatchTeam; teamB: MatchTeam }` - `MatchMap`: `{ contentId; name; preview }` - `BracketTeam`: `{ name; logo }` - `BracketRound`: `{ title; teams: BracketTeam[] }` - `Bracket`: `{ stages: string[]; rounds: BracketRound[] }` - `HistoryItem`/`UpcomingItem`: `{ title; cover; startTime; url }` - `GameCfg`: 包含 `logo`, `teams`, `matches`, `matchMaps`, `commentators`, `operators`, `bracket` - `LivePayload`: - 核心:`type, env, title, description, roomId, startTime, endTime, cover` - 扩展:`gameCfg?: GameCfg, history?: HistoryItem[], upcoming?: UpcomingItem[]` ### LiveUpdater 相关类型 - `LiveUpdatePlayer`: `{ id; score?; hp?; kda? }` 可选实时字段由数据上报方自行决定。 - `LiveUpdateSide`: `{ score; players: LiveUpdatePlayer[] }` - `LiveUpdatePayload`: `{ startTime; teamA; teamB; highlight?; danmaku? }` - `LiveUpdatePayloadArray`: `LiveUpdatePayload[]` 这些类型已集中在 `server/src/lib/types.ts` 并由 `server/src/App.ts` 统一导出。 高光/弹幕类型要点: - `LiveUpdateHighlight`: - `reason: string` 高光原因/提示 - `teamNames?: string[]` 关联队伍名称(与 `MatchTeam.name` 对齐) - `playerIds?: number[]` 关联选手 - `immediate?: boolean` 平台是否立刻放大 live_mini - `LiveDanmaku`: - `text: string` 文本内容 ## LiveUpdater API(上报器) - `new LiveUpdater(opts)` - opts.key: string(必填)用于后端存储键名 - opts.endpoint?: string 默认 `https://api.box3lab.com/set_redis_value` - opts.headers?: Record - opts.maxRetries?: number 默认 0 - opts.retryDelayMs?: number 默认 500 - 内置提交节流:两次提交至少间隔 10ms - `static buildPayloadFromMatch(match, scores?, playerExtras?) => LiveUpdatePayload` - 从 `Match` 构建可提交的载荷(不发送) - `pushFromMatch(match, scores, playerExtras?) => Promise` - 将单场加入暂存并提交。若之前已提交过历史场次,会自动与历史合并后一起提交 ### 高光与弹幕(即时接口) - `pushHighlightNow(match, highlight) => Promise` - 立刻上报“单一高光”。平台可据此放大 live_mini 等。 - 参数 `highlight: LiveUpdateHighlight`,示例见下。 - `stopHighlightNow(match) => Promise` - 立刻取消当前高光(置空并上报)。 - `pushDanmakuNow(match, danmaku) => Promise` - 立刻发送一条弹幕,平台侧会展示该弹幕。 示例: ```ts // 触发高光 await updater.pushHighlightNow(match, { reason: 'ACE', teamNames: ['Team A'], playerIds: [12345], immediate: true, }); // 立刻取消高光 await updater.stopHighlightNow(match); // 发送弹幕 await updater.pushDanmakuNow(match, { text: '加油!稳住别浪!', }); ``` ## 实用建议 - 做快照或统计前建议 `await refreshNow()` 确保数据最新 - 本地调试可 `await useTestData(true)` + `setNowSecOverride()` - 上线前请确保 `await useTestData(false)` ## 示例代码 - `server/src/examples/pushCurrentMatchDemo.ts` - 最简示例:获取当前场次并上报比分 - `server/src/examples/pushPlayerExtrasDemo.ts` - 为当前场次上报玩家扩展字段(例如 score/hp/kda) - `server/src/examples/pushMultipleMatchesDemo.ts` - 连续覆盖多场(历史 + 当前),第二次提交自动把两场一起提交 - `server/src/examples/pushDryRunDemo.ts` - Dry-Run:仅构造并打印 payload,便于联调校验 - `server/src/examples/highlightDanmakuDemo.ts` - 触发高光/弹幕 ## 获取已上报的数据(查询接口) - 平台查询接口(GET):`https://api.box3lab.com/get_redis_value?key=<你的key>` - 例如本仓库示例使用的 key:`demo_player_extras` ## 变更日志 ### 0.2.0 - 高光/弹幕时间戳由 SDK 内部自动填充,调用方不再传 `ts`。 - 类型清理:移除公共接口中的 `ts` 与 `senderId`。 - 提交流程节流:两次提交至少间隔 10ms。 - 更新 README 与示例代码以匹配上述变更。