本文面向前端 / 全栈开发者,说明如何在生产环境中稳定接入 HappyOyster Web SDK 。
具体 API 需参考 HappyOyster Web SDK API Reference。
最小接入示例
如果你已经从服务端拿到了百炼临时 api-key token 和 Travel ticket,前端接入 SDK 的最小链路大致如下:
import { HappyOysterEngine, isSdkError } from "@happy-oyster/js-sdk";
const engine = new HappyOysterEngine({
APIHost: "open-platform.example.com",
model: "happyoyster-1.0-adventure", // 必填;此处以 Adventure 模型为例
logLevel: "warn",
});
engine.updateToken(token);
const videoElement = document.querySelector("video") as HTMLVideoElement;
const travel = engine.createTravel({
ticket,
videoElement,
maxExperienceTimeSec: 90, // 可选,仅 Adventure 生效:60 / 90 / 120
});
const offStatus = travel.on("statusChanged", (status) => {
// 用 status 驱动 loading、按钮禁用、播放器状态等 UI。
});
const offError = travel.onError((error) => {
// 这里通常做日志、toast;如果是 token 过期,重新向服务端申请 token 后 updateToken。
if (isSdkError(error)) console.error(error.code, error.message);
});
try {
await travel.start();
// running 后再通过 travel.can(action) 开放操控或 Directing 输入。
} finally {
offStatus();
offError();
await travel.end();
}
这段代码只覆盖 SDK 侧。创建世界、等待世界 ready、申请 ticket、申请百炼临时 api-key token 都应该在业务服务端或经由业务服务端完成。
端到端链路 Overview
一次完整的体验主要涉及以下参与方。
参与方
参与方 | 职责 |
|---|---|
业务前端 | UI、 |
业务服务端 | 保管主 API Key、代调开放平台世界类 API、下发百炼平台临时凭证 |
HappyOyster OpenApi | 提供 HappyOyster 世界模型所需 API 的对外接口。 |
HappyOyster SDK | 业务前端所调用的 SDK,主要负责客户端侧管理 Travel 会话,视频流接入等。 |
故事链路
阶段 | 执行方 | 动作 | 说明 |
|---|---|---|---|
① 创建世界 | 业务服务端 → HappyOyster OpenApi |
| 返回 |
② 等待就绪 | 业务服务端 → HappyOyster OpenApi |
| 轮询至 |
③ 体验凭证 | 业务服务端 → HappyOyster OpenApi |
| 返回 |
④ 申请临时 api-key token | 业务服务端 → HappyOyster OpenApi |
| 返回临时 api-key |
⑤ 下发临时 api-key token 以及体验凭证 | 业务前端 → 业务服务端 → 业务前端 | 申请临时 api-key 和 体验凭证 | 前端拿到 |
⑥ 进入体验 | 业务前端 → HappyOyster SDK | 见下文「SDK 启动子链路」 |
|
SDK 游玩子链路(阶段 ⑥ 展开)
启动一个 Travel
调用顺序要点
步骤 | API | 说明 |
|---|---|---|
1 |
| 同一 |
2 |
| 在 |
3 |
| 必须传入 |
4 |
| 建议在 |
5 |
| 成功后进入 |
在 Adventure 模式下进行游玩
在 Directing / 角色演绎模式下进行游玩
Adventure、Directing 和角色演绎可以先按下表理解:
模式 | 主要能力 | 常见场景 | 注意点 |
|---|---|---|---|
Adventure( |
| 键盘、摇杆、按钮等实时操控 | 只在 |
Directing( |
| 用自然语言推进剧情、控制播放节奏 | 暂停、恢复和回退由会话能力决定;先用 |
角色演绎( |
| 演出型体验与画幅配置 | 暂停、恢复由会话能力决定;不支持 |
业务服务端相关实践(概要)
服务端这边只抓住几件事就好:
- 主 API Key 仅驻留业务服务端,浏览器只拿短效
token(Demo:POST /server-api/temp-api-key) - 体验凭证由服务端代申请,按世界 / 用户鉴权后再下发
ticket;不要在前端硬编码或长期缓存 - 世界未 ready 不要签发凭证,否则 enter-travel 可能失败或体验不完整
- 业务服务端与 SDK 指向同一开放平台环境:业务服务端可使用完整 OpenAPI 地址;SDK 的
APIHost只传裸 host,例如open-platform.example.com
SDK 核心概念
在端到端链路中,SDK 只承担「进入 Travel → 播放 → 操控 → 结束」段。两个核心对象:
对象 | 作用 |
|---|---|
| 配置 |
| 一次会话: |
会话状态(statusChanged):
idle ──start()──► prepare ──video 可播放──► running ⇄ paused ──end()──► idle
三条约束(后文展开):
- 每个 Engine 同时只允许一个 active Travel
videoElement必须在createTravel时传入- 百炼临时 api-key token 与 travel ticket 用途不同(见「故事链路」阶段 ④⑤ 与「双凭证模型」)
初始化与 Token 管理
Engine 实例化
必须- 配置
APIHost(开放平台裸 host,不含https://、协议头或路径,例如open-platform.example.com) - 同一
APIHost + model复用一个HappyOysterEngine;切换 host 或模型前,先结束当前 Travel,再使用目标配置的 Engine
- 开发环境
logLevel: 'debug',生产环境'warn'或'none' - Engine 在会话编排层创建,不用每次 start 都 new
常用配置可以先按这张表理解:
配置项 | 必填 | 建议 |
|---|---|---|
| 是 | 只传裸 host,例如 |
| 是 | 必填,无默认值;与业务服务端创建世界、签发 ticket 使用的模型保持一致。Engine 创建后固定,不会根据 |
| 否 | 可以构造时传,也可以后续用 |
| 否 | 开发环境用 |
| 否 | 网络或渲染链路较慢时可调大;超时后建议给用户重试入口 |
- 在 render / 热路径中重复
new HappyOysterEngine() - 传入完整 URL(例如带
https://)或省略APIHost后再调用 SDK 后端接口
双凭证模型
对应故事链路阶段 ③④⑤:
凭证 | 来源 | 设置方式 | 用途 |
|---|---|---|---|
百炼临时 api-key token | 业务服务端下发短效 Key |
| SDK 与开放平台请求认证 |
Travel ticket | 业务服务端代申请体验凭证 |
| Travel 的单次游玩凭证 |
- 在
travel.start()之前完成两者准备 - SDK 不持久化、不自动刷新 token
- token 过期或心跳
onError后,经业务服务端重新申请并updateToken
- 混用两种 token
- 把主 API Key 放到业务前端
Travel 生命周期
创建与启动
必须createTravel({ ticket, videoElement })—ticket为单次 Travel 凭证,videoElement在此时传入- 先注册
statusChanged/onError,再await travel.start(),保证错误和状态感知
prepare阶段展示 connecting UI,防止重复点击;监听travelInfoReady尽早读取会话信息,firstFrameGenerated可用于展示首帧占位图
- DOM 元素尚未就绪时 create Travel
运行中
statusChanged驱动 UI:prepare= 等待画面,running= 可交互pause/resume/rewind的使用场景与前置条件(由会话能力决定;rewind需在paused且流已中断)
结束与重建
必须- 结束会话:
await travel.end() - 创建新 Travel 前确保上一次已 end
- 页面卸载 / 路由离开时在 cleanup 中
travel.end()
- 失败后走「end → 清理监听 → 从故事链路 ③ 或 ⑤ 重试」
- 重试时重新申请体验凭证(若一次性)
- 不 end 就 createTravel 第二次(同步 throw)
- 页面离开未 cleanup,导致 RTC 资源泄漏
视频元素与浏览器策略
必须<video>设置playsInline(iOS 必需)- 了解浏览器自动播放策略对
autoPlay/muted的影响
prepare阶段用首帧图 + 遮罩,running后隐藏- 按需配置
streamReadyTimeout(默认 15000ms),超时给出重试入口 - 播放器容器预留固定宽高比
- video 未挂载到 DOM 时传入元素引用
- 忽略
paused/ 断流,UI 仍显示可交互
事件订阅与错误处理
事件订阅
必须- 事件挂在
Travel上,不是HappyOysterEngine上 - 保存
travel.on(...)/travel.onError(...)返回的 unsubscribe 函数
- 集中注册、集中清理;切换 Travel 前先取消旧监听
事件 | 用途 |
|---|---|
| 状态展示、画面就绪判断( |
| RTC 连接前获取 |
| 有非空首帧时,在 |
| 日志、toast、触发 token 刷新 |
错误分层
- 启动失败:
travel.start()reject(enter-travel / RTC / 超时) - 运行时错误:
travel.onError(RTC、心跳鉴权失败等) - 业务服务端 / 开放平台错误在故事链路 ①–④ 阶段由业务层处理,不进 SDK
- 只 catch
start()不监听onError - 重复注册监听但不 unsubscribe
指令发送:Command 与 Instruct
sendCommand(实时操控)
sendCommand() 用于 Adventure(模式 1)下的实时操控,经 RTC DataChannel 发送,仅在会话处于 running 状态时可用。
SDK 最多约每 42ms(约 24FPS)发送一次指令:距上次发送已超过一个周期时立即发送;同一周期内调用多次时,只发送最后一次调用的完整指令。translation、rotation、interaction 是三个独立的控制维度,未传字段按 None 发送,SDK 不会跨调用合并字段,也不会周期性重复上一条指令或自动发送 None。
跳跃、攻击等单次触发动作调用一次即可:
await travel.sendCommand({ interaction: "Jump" });
持续动作
移动、持续转动视角等动作,按住期间应持续调用。前端应用无需自行按 42ms 节流,SDK 会将高频调用合并为约 24FPS 的发送频率;松开时应显式发送一次 None 复位:
// 按住期间持续调用
void travel.sendCommand({ translation: "Front" });
// 松开时停止移动
void travel.sendCommand({ translation: "None" });
避免
prepare阶段大量发送- 用 sendCommand 发送自然语言实时导演内容
sendInstruct(实时导演指令)
必须- Directing(模式
2)或 角色演绎(模式3,非scriptlist)会话;await travel.sendInstruct({ content })
- 发送前检查 content 非空,防止重复提交
- 根据返回的
accepted等字段反馈用户
常见问题与排障
现象 | 常见原因 | 建议处理 |
|---|---|---|
初始化就报参数错误 |
| 只传裸 host,例如 |
|
| 重新从服务端申请 |
运行一段时间后 | 百炼临时 api-key | 前端向业务服务端重新申请 token,然后调用 |
画面一直 loading 或 | 远端推流未就绪、网络慢、 | 给用户重试入口;必要时调大 |
第二次 | 上一次 Travel 没有 | 页面切换、重试、退出时都调用 |
按键或指令没有反应 | 当前状态不是 | 用 |
排查问题时建议同时记录三类信息:SDK ErrorCode、当前 TravelStatus、业务服务端请求日志。这样基本可以判断问题是在凭证、状态、网络,还是渲染链路上。