动画直播组件接入
一个可以嵌进你网页或 App 的比赛动画直播:舞台放 2.5D 平面动画或 3D 球场,四周是比分条和数据页签。两种画面共用同一套布局,换画面只改配置,嵌入代码不用动。数据是本平台的实时动画流,球点、场面和事件随比赛推送。
两种画面
| 画面 | 说明 | 适合 |
|---|---|---|
| 2.5d | 俯视透视的平面动画:球点、场面标签(危险进攻 / 进攻 / 控球)、轨迹线、进攻区域、事件卡片。几十 KB,任何手机都流畅。 | 首页卡片、列表页、App 内嵌 |
| 3d | 真实球场和 22 名球员按实时球点跑位,球衣颜色取自两队真实球衣,带观众、声音和进球 / 牌 / 角球等事件镜头。首次加载约 11 MB(浏览器缓存后很快)。 | 比赛详情页、专题页 |
布局与数据模块
| 布局 | 说明 |
|---|---|
| standard | 标准:舞台 + 比分条 + 数据页签 |
| wide | 宽屏:舞台在左,数据面板在右侧常驻(窄于 560px 时自动改成上下排) |
| compact | 紧凑:只有舞台和比分条 |
| mobile | 手机竖屏:390 宽,页签切换 |
| 模块 | 说明 |
|---|---|
| scorebar | 比分条:队徽、队名、比分、比赛时钟 |
| sceneLabel | 场面标签:危险进攻 / 进攻 / 控球,带主客(仅 2.5D) |
| eventCard | 事件卡片:进球、角球、牌、换人;3D 里是画面中央的横幅 |
| trail | 轨迹线:最近 5 个场面点(仅 2.5D) |
| zone | 进攻区域着色(仅 2.5D) |
| stats | 技术统计:控球率、进攻、危险进攻、射门、射正、角球、黄红牌 |
| timeline | 事件时间线:进球、牌、换人、角球 |
| scene | 场面聚合:近 5 分钟危险进攻、进攻、控球和压迫指数 |
| momentum | 势能:每分钟攻势(打开组件后累计) |
| tlive | 文字直播:逐条中文播报 |
| lineup | 阵容:首发 / 替补、号码 |
| ftPanel | 半场 / 完场面板:中场和完场时盖在舞台上的统计 |
场面标签、轨迹线、进攻区域只在 2.5D 里有;3D 里是球场和球员,这三个模块自动关闭。3D 的球和事件是实时数据;球员跑位由服务端根据球点、场面和事件推演,所有观众看到同一场画面,但不是真实跑位数据。模块和模板受套餐限制,以下面这个接口返回的为准: /api/v1/widget/options
接入步骤
- 开通:联系商务开通动画直播组件,登记允许嵌入的域名(如
*.example.com)、可用画面和同时观看上限。开通后你的 API Key 就能调/api/v1/widget/*。 - 配置:在 组件控制台 里选画面、模板、样式、数据模块和布局,右边是真实比赛的实时预览;保存并发布后得到配置 id(
wgt_开头)。 - 换令牌:你的服务端用 API Key 调 POST /api/v1/widget/token,传配置 id 和嵌入页面的 origin,拿到默认 2 小时有效的令牌。API Key 只放服务端,不要写进网页或 App 包。
- 嵌入:页面里放 iframe,地址是
/widget/anim?cfg=<配置 id>&match=<比赛 id 或 auto>&token=<令牌>。match是本平台的比赛 id;auto自动选一场正在进行的热门比赛。 - 推送方式:组件默认用 SSE 接收实时数据;地址加
&transport=ws改用 WebSocket(连不上时自动退回 SSE)。两种方式内容相同,3D 画面的球员跑位帧也一起推送,每秒约 10 帧。
没有服务端?看下面的 App Key 嵌入。在 Flutter、iOS、Android App 里嵌入,见 SDK 与客户端库 →
代码
① 服务端换令牌
Terminal
curl -X POST https://api.sports.csap.ai/api/v1/widget/token \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"config":"wgt_1a2b3c4d","origin":"https://www.example.com","ttl":7200}'
# → {"code":0,"data":{"token":"…","expires_at":1794240000,"embed_url":"/widget/anim?cfg=wgt_1a2b3c4d&token=…"}}② 页面嵌入(令牌由你的服务端下发)
HTML
<iframe id="live-anim" style="width:100%;height:640px;border:0" allow="autoplay; fullscreen"
src="https://api.sports.csap.ai/widget/anim?cfg=wgt_1a2b3c4d&match=auto&token=<令牌>"></iframe>
<script>
// 组件告诉页面它需要多高
addEventListener('message', (e) => {
const f = document.getElementById('live-anim');
if (e.source === f.contentWindow && e.data && e.data.type === 'anim-widget-size') f.style.height = e.data.height + 'px';
});
</script>没有服务端?用 App Key 直接嵌入 新
在控制台「动画直播组件 → App Key」新建一个 pk_ 开头的 App Key,填上你网站的域名。它只能加载组件、不能调数据接口,可以写在页面源码里;页面带上比赛 ID 就能播,不需要换令牌的服务端。 去控制台新建 App Key →
① iframe:贴一段 HTML
HTML
<!-- App Key 可以放在页面源码里;只在白名单域名下能播 -->
<iframe src="https://api.sports.csap.ai/widget/embed?app_key=pk_3f9a…&match=4651805&view=3d"
style="width:100%;aspect-ratio:16/9;border:0" allow="autoplay; fullscreen"></iframe>② 脚本:自动调高度,并把进球 / 完场转成页面事件
HTML
<!-- 脚本在它所在的位置建 iframe -->
<script src="https://api.sports.csap.ai/widget/embed.js" data-app-key="pk_3f9a…"
data-match="4651805" data-view="2.5d" async></script>
<script>
document.addEventListener('animwidget:goal', (e) => console.log('进球', e.detail.score));
document.addEventListener('animwidget:end', (e) => console.log('完场', e.detail.score));
document.addEventListener('animwidget:error', (e) => console.warn(e.detail.reason)); // 没能播放的原因
</script>参数
| 参数 | 说明 |
|---|---|
| app_key | 必填。控制台里的 App Key(pk_ 开头)。脚本嵌入写成 data-app-key。 |
| match | 本平台的比赛 ID;不传或传 auto 时由平台自动选一场正在进行的热门比赛(这种加载不能再另指一场)。脚本嵌入写成 data-match。 |
| view | 可选,2.5d 或 3d;不传用配置里的画面。只能选套餐已开通的画面。 |
| lang | 可选,zh 或 en;不传用配置里的语言。 |
| exp / sig | 只在签名模式下需要:过期时间(Unix 秒)和签名,见下文。 |
| data-height | 只用于脚本嵌入:固定高度(像素);不传时按组件内容自动调高度。 |
脚本嵌入的页面事件(在 document 上监听,数据在 e.detail 里)
| animwidget:ready | 组件连上比赛:match_id、home、away |
| animwidget:goal | 进球:side(1 主队 / 2 客队)、score、minute |
| animwidget:end | 完场:score |
| animwidget:error | 没能播放:reason 是下面「被拒原因」里的代号 |
App Key 的安全
| 域名白名单 | 必填,且必须在平台给你开通的组件域名之内,只写主机名(字母、数字、连字符和点,可用 *.example.com)。服务端核对嵌入页面的 Origin / Referer;组件页带 Content-Security-Policy: frame-ancestors,别的网站框不住,也换不到令牌。不要给 iframe 设置 referrerpolicy="no-referrer",否则无法确认来源、会被拒。 |
| 短期令牌 | 每次加载换一个绑定你域名、2 小时有效的令牌,到期自动续;App Key 本身不能调任何数据接口。令牌只能由组件页自己使用:从别的网站的页面拿它去连数据会被拒。 |
| 限速与上限 | 按 Key、按访客 IP 限速;可设每日观看上限和并发上限,超出时组件位置显示「暂不可用」。一次观看 = 一位访客打开一次页面(同一页面断线重连不重复计);同一个令牌被别的地址拿去播,按另一次观看计。 |
| 停用 / 轮换 | 停用后新的加载立即被拒,正在播放的页面几秒内停止;轮换后旧 Key 立即失效。组织被暂停或组件被关闭时同样几秒内停止。被平台停用的 Key 只能查看:不能自行启用、修改、轮换或删除,也不能另建 Key 接同一个网站,要恢复请联系商务。 |
| 签名模式(可选) | 开启后页面必须带服务端签名:你的服务端用 App Secret 计算 HMAC-SHA256(secret, "比赛ID:过期时间") 的十六进制值,页面加上 exp 和 sig 两个参数。改了比赛或过期时间,签名就不对;过期时间最长 7 天。App Secret 只在创建、轮换或重新生成时显示一次,只放服务端。 |
③ 签名模式:服务端算签名(Node.js 示例)
Node.js
const crypto = require('crypto');
const secret = process.env.WIDGET_APP_SECRET; // App Secret 只放服务端
const match = '4651805';
const exp = Math.floor(Date.now() / 1000) + 3600; // 过期时间(Unix 秒),最长 7 天
const sig = crypto.createHmac('sha256', secret).update(match + ':' + exp).digest('hex');
// 把 exp 和 sig 写进页面:
// https://api.sports.csap.ai/widget/embed?app_key=pk_3f9a…&match=${match}&exp=${exp}&sig=${sig}被拒原因(「暂不可用」页左下角的代号、响应头 X-Widget-Refused、animwidget:error 的 reason)
| unknown | App Key 不存在(写错、已轮换或已删除) |
| domain | 嵌入页面的域名不在白名单里,或无法确认来源;直接在地址栏打开嵌入地址也算 |
| disabled | App Key 已停用,组件未开通,或组织已被暂停 |
| quota | 今日观看次数达到上限 |
| concurrent | 同时观看人数达到上限 |
| rate | 访问太频繁(按 Key、按 IP;同一地址对所有 Key 的加载也有上限) |
| signature | 签名模式下签名缺失、不对或已过期 |
| scope | 比赛 ID 不对,或不在这个 Key 允许的比赛范围内 |
| config | 绑定的配置还没发布,或套餐未开通该画面 |
页面消息
组件通过 postMessage 告诉嵌入它的页面(令牌嵌入和 App Key 嵌入都会发;用脚本嵌入时脚本已经替你处理,并转成 animwidget:* 页面事件):
| 消息 | 说明 |
|---|---|
| anim-widget-size | 字段 height:组件内容的高度(px)。布局或页签变化时都会发,把 iframe 的高度设成它就不会出现滚动条。 |
| anim-widget-event | 字段 event 和 data。event 是 ready(连上一场比赛:match_id、home、away)、goal(进球:side 为 1 主队或 2 客队、score、minute)或 end(完场:score)。 |
| anim-widget-error | 字段 reason 和 height:组件没能播放或被停止,reason 是上面「被拒原因」里的代号。令牌嵌入也会发:播放中组件被关闭、组织被暂停或 App Key 被停用 / 轮换时,组件在舞台位置显示提示,并发一条 reason 为 disabled 的消息。 |
安全与限制
- 令牌绑定配置和 origin;组件页只允许被这个 origin 嵌入(响应头
Content-Security-Policy: frame-ancestors),换个域名用不了。 - 令牌默认 2 小时、最长 24 小时;过期后组件提示刷新,页面重新向你的服务端要令牌即可。
- 同时观看人数超过开通的上限时,新打开的组件返回 429。
- 组件被关闭或组织被暂停时,两种嵌入方式都会停:新的加载被拒,正在播放的页面几秒内停止并显示提示。
- 没有动画数据的比赛(未开赛、已结束或这场没有动画)组件不会空等:比分条显示真实比分,舞台写明「比赛未开始 / 比赛已结束 / 暂无动画数据」。
- 配置只在发布后生效;改画面、模板或布局后重新发布,已嵌入的页面刷新即可看到。
相关接口
POST /api/v1/widget/token | 换嵌入令牌(只在服务端调用) |
GET /api/v1/widget/options | 本账户可用的画面、模板、布局和模块 |
GET /api/v1/widget/configs | 本账户保存的配置 |
GET /api/v1/widget/stats | 当前在线和今日观看 |
GET /api/v1/widget/app-keys | App Key 列表、今日观看和被拒次数;新建和修改用同一路径的 POST |