全部產品
Search
文件中心

Alibaba Cloud Model Studio:HappyOyster Android SDK API 參考

更新時間:Sep 23, 2026

HappyOyster Android SDK 入口為單例物件 HappyOyster,除 initialize、updateToken、attachVideo、sendCommand 外,業務方法均為 suspend,失敗時擲回 SDKError。

當呼叫端 coroutine 被取消時,SDK 會取消該呼叫仍在進行的 HTTP 請求(如有),並原樣傳播 CancellationException,不會轉換為 SDKError。取消並不代表伺服器已經受理的請求會被回滾。

整合流程、安裝、最佳實務請參閱 HappyOyster Android SDK 整合指南。

核心名詞概念

名詞

含義

token

百煉閘道 API Key:由您的 App 透過 updateToken(token) 注入為 Bearer token。SDK 只儲存最新一個,不持久化、不刷新;Key 變更後由您再次注入。閘道要求包括 startTravel 在內的 SDK 請求均使用此 Bearer。Bearer Key 過期或無效時,SDK 擲回 SDKError(101002);此時應重新獲取並注入 Bearer Key(updateToken),而非重新換取 ticket。安全建議:用戶端 Bearer 宜使用伺服器簽發的短期 token,而非將長期 API Key 打包進 App 或寫入程式碼儲存庫。

ticket

一次性體驗憑證:由您的伺服器呼叫開放平台換取並下發給用戶端,僅用於一次 startTravel;體驗結束(正常或異常)後即失效,不可重複使用。ticket 層級的憑證錯誤由六位數伺服器 code 識別(如 401010 = ticket 無效/過期,401011 = ticket 已使用),SDK 原樣透傳。

環境需求

項目

要求

minSdk

24(Android 7.0)及以上

compileSdk

36

語言

Kotlin(協程 suspend API)

ABI

arm64-v8a / armeabi-v7a(即時通訊引擎包含 native 程式庫)

網路

需要可存取公網

核心概念

概念

說明

World

AI 世界,包含角色與場景。由您的伺服器建立與管理。

Travel

一次即時體驗。基本生命週期:init → pending → running → completed(失敗為 failed)。在即時導演與角色演繹模式下,若體驗支援暫停,running 可透過 pauseTravel 進入 paused,再透過 resumeTravel 回到 running(即時導演另可用 rewindTravel 回溯,回溯成功後伺服器自動恢復);running ⇄ paused 可多次循環。

模式

adventure(世界探索,裝置/指令互動)、directing(即時導演,文字驅動劇情)或 acting(角色演繹,文字驅動劇情,播放畫幅由伺服器在建立時固定)。SDK 統一以這三個值對外。

即時視訊

startTravel 成功後由 SDK 自動建立與維護,無需您手動連線/斷線;於 endTravel 時自動釋放。

概覽

HappyOyster 方法

方法

說明

initialize(context, config)

初始化 SDK;允許在 idle 時重新初始化,若 Travel 正在 starting、active 或 ending 時重新初始化,將以 103004 拒絕。

updateToken(token)

注入/更新百煉閘道 API Key(Bearer token)。

startTravel(ticket)

使用一次性憑證開始體驗,並自動建立即時視訊連線。

pauseTravel()

非同步暫停體驗(即時導演與角色演繹,且體驗支援暫停);受理後真正暫停以 onStatusChanged(Paused) 為準。

resumeTravel()

恢復已暫停的體驗(即時導演與角色演繹);內部包含 3× 重試退避機制。

rewindTravel(rewindToSec)

回溯到指定秒數(僅即時導演、狀態為 paused)。秒數須為 4 的整數倍。角色演繹 不支援回溯。

sendInstruct(content)

傳送文字指令以驅動劇情(即時導演與角色演繹,running 或 paused 狀態)。

sendCommand(command)

傳送方向/視角/動作控制指令(世界探索模式,僅限 running 狀態;需於主執行緒呼叫)。角色演繹模式不可用。

attachVideo()

傳回用於播放的 SurfaceView,掛載至版面配置即可渲染(需於主執行緒呼叫)。

endTravel()

結束體驗,自動中斷即時連線並釋放所有工作階段資源。

VERSION

SDK 版本字串(SemVer),編譯期常數。

事件

事件

說明

onStatusChanged(status)

體驗狀態變更(包含即時視訊生命週期),值請參閱 TravelStatusValue。

onError(error)

內部自動流程發生錯誤時回呼;致命錯誤會同時終止本次體驗。

關鍵資料類型

類型

說明

SDKConfig

SDK 初始化設定(apiHost、model 均為必填,以及 logLevel、logcatEnabled、logHandler、callbackTimeoutMs)。

TravelStatusValue

體驗狀態:Init / Pending / Running / Paused / Failed / Completed。

ModeValue

體驗模式:adventure / directing / acting。

CreationModelValue

世界建立模型:Simple(預設,instruct 驅動)/ ScriptList(結構化劇本,不支援 sendInstruct)。

StartTravelData

startTravel 返回,包含 mode、version、creationModel、aspectRatio、maxExperienceTimeSec 等中繼資料。

AdventureCommand

sendCommand 參數,包含 translation / rotation / interaction 三欄位。

SDKError

SDK 錯誤,包含 code: Int 與可選的 raw: Any?。

HappyOyster.initialize

初始化 SDK,應在呼叫其他 API 前完成(建議在 Application.onCreate)。config 為必填參數,須攜帶您帳號的百煉 apiHost,並透過 model 指定拆分後的模型名稱(happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure,與所用 Open API 入口一致)。SDK 按以下規則拼接請求位址:

https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}

model 沒有預設值:建構 SDKConfig 時遺漏該參數會編譯失敗;傳入空白值則 initialize 同步擲回 SDKError(100002),且不會建立或替換 runtime,也不會發起網路請求。SDK 只持有 application context,不持有 Activity。僅當上一個 runtime 為 idle 時允許重新初始化;切換模型時須在 idle 狀態使用新 model 重新初始化。若 Travel 正在 starting、active 或 ending,須先等待 endTravel() 完成。

說明

地區合規提示:
  • 為支援適用法律法規及資料合規要求的落實,若您的目標使用者包含美國使用者,您必須在 SDK 初始化時為面向該等使用者的服務設定美國地區 API Host。
  • 開發者應確保設定正確,並依法承擔因未依上述要求設定所產生的相應責任。

簽章

fun initialize(context: Context, config: SDKConfig)

參數

欄位

類型

是否必填

描述

context

Context

是

建議傳入 applicationContext。

config

SDKConfig

是

SDK 設定,必須包含必填且無預設值的 apiHost 與 model。

傳回值

無回傳值。

錯誤

code

說明

100002

model 為空白;初始化同步失敗,SDKError.raw 為 SDKConfig.model must not be blank,不建立或替換 runtime,也不發起網路請求。

103004

Travel 正在 starting、active 或 ending,拒絕重新初始化。請等待 endTravel() 後重試。

HappyOyster.updateToken

注入/更新百煉閘道 API Key,執行緒安全;在 initialize 後可隨時呼叫。SDK 會將最新 token 用於 startTravel 及後續體驗控制請求。ticket 是一次性體驗憑證,不應與 Bearer token 混用。

簽章

fun updateToken(token: String)

參數

欄位

類型

是否必填

描述

token

String

是

百煉閘道 API Key,作為 Bearer token 使用。

傳回值

無回傳值。

錯誤

code

說明

100001

SDK 尚未初始化。

HappyOyster.startTravel

使用一次性 ticket 開始一次體驗。成功後 SDK 自動建立即時視訊連線並開始內部狀態輪詢,狀態透過 onStatusChanged 透出。

  • ticket 為一次性憑證,呼叫後即視為已消耗。
  • 同一 App 同一時刻僅允許一個併發 Travel;已有體驗進行中時再次呼叫會擲回 SDKError(103004),SDKError.raw 中僅包含當前活躍 ticket 的去識別化摘要供診斷,不包含完整 ticket。
  • 可選擇傳入 maxExperienceTimeSec 限制本次體驗最大時長(僅世界探索 / adventure 模式生效),詳見參數。

StartTravelData.creationModel(CreationModelValue,預設 CreationModelValue.Simple):指示該世界的建立模型,即劇本內容的管理方式。simple(預設)為普通 instruct 驅動的世界,世界探索類世界也會標準化為此值。scriptlist 為結構化 ScriptList 世界——劇本內容不透過本 SDK 存取。在 ScriptList 模式(creationModel == CreationModelValue.ScriptList)下呼叫 sendInstruct 會被拒絕,並擲回 SDKError(103002)。該欄位預設值為 simple。

StartTravelData.aspectRatio(String?):伺服器為本次工作階段分配的播放畫幅,格式如 width:height。僅角色演繹(acting)非空——"9:16"(直式,伺服器建立時的預設值)或 "16:9"(橫式);世界探索與即時導演模式、以及伺服器未下發時為 null。SDK 原樣保留未知取值(不收斂為 null),宿主須將不認識的值等同 null 處理並回退到自己的預設方向;SDK 自身不使用該欄位。

該值隨 startTravel() 的返回值交付:請在 startTravel() 返回後、呼叫 attachVideo() 把返回的檢視掛進版面配置之前據此確定播放容器方向——SDK 只有在宿主掛載檢視後才開始綁定渲染遠端串流,所以此刻定方向仍趕在首幀渲染之前(但不保證發生在 SDK 加入即時房間之前)。遠端檢視以裁剪填充(clip-to-fill)方式綁定,容器方向與該值不一致會裁掉畫面而不是留黑邊。

簽章

suspend fun startTravel(ticket: String): StartTravelData
suspend fun startTravel(ticket: String, maxExperienceTimeSec: Int?): StartTravelData

參數

欄位

類型

是否必填

描述

ticket

String

是

一次性體驗憑證,由您的伺服器端下發。

maxExperienceTimeSec

Int?

否

本次體驗的最大時長(秒),到期後世界端自動結束工作階段。僅世界探索(adventure)模式生效,即時導演與角色演繹忽略此值。合法取值由伺服器設定,當前為 60 / 90 / 120;傳 null 或使用不帶該參數的多載時,採用伺服器預設值(當前為 60)。傳入不支援的值將由伺服器拒絕 startTravel 並返回 400000,此時擲回 SDKError(400000) 且不會建立活躍體驗。SDK 不在本機驗證取值,允許級距以伺服器為準。

傳回值

傳回 StartTravelData,包含體驗中繼資料(mode、version、creationModel、aspectRatio 等)。請先檢視傳回的中繼資料再決定如何互動(如 travel.mode、travel.creationModel、travel.version),角色演繹還需按 travel.aspectRatio 設定播放容器方向。

錯誤

code

說明

401010

ticket 無效或已過期

401011

ticket 已被使用

400000

輸入參數無效(如 maxExperienceTimeSec 取值不在伺服器允許的級距內),本次 startTravel 啟動失敗

403002

世界狀態非就緒

500001

資源分配/服務內部失敗

103004

已有 Travel 正在 starting、active 或 ending,或併發呼叫 startTravel(同一時刻僅允許一個 Travel;SDKError.raw 僅含當前活躍 ticket 的去識別化摘要)

HappyOyster.pauseTravel / HappyOyster.resumeTravel

暫停 / 恢復體驗。SDK 同時僅管理一個 Travel,encryptedTravelId 由 SDK 內部自動取得,呼叫端無需傳入。

前置條件:

  • pauseTravel:僅即時導演(directing)或角色演繹(acting)、且狀態為 running 時可呼叫。
  • resumeTravel:僅即時導演(directing)或角色演繹(acting)、狀態為 paused 時可呼叫。

並非所有體驗都支援暫停 / 恢復:體驗須報告本模式要求的版本識別碼(StartTravelData.version,即時導演為 storyV2、角色演繹為 actingV2;比較時忽略首尾空白與大小寫),否則 pauseTravel 與 resumeTravel 都會返回 103002。角色演繹支援暫停 / 恢復,但 不支援 rewindTravel。

暫停是非同步的(重要):pauseTravel 是一個「重」操作——呼叫成功(方法返回)只代表暫停已受理,此刻體驗尚未真正暫停。只有當 onStatusChanged 回呼報告 paused 時,才算真正暫停。因此請以該回呼驅動宿主狀態機和呼叫門控:在「呼叫 pauseTravel」到「收到 paused 回呼」之間可將本機狀態標記為 pausing;收到 paused 後才允許發起 resumeTravel 或 rewindTravel。不要把 pauseTravel 的返回當作已暫停。

即時連線的處理:真正暫停後 SDK 會斷開即時連線;恢復時 SDK 會使用同一次 startTravel 時下發的憑證自動重新加入並恢復畫面,無需宿主干預。

暫停拆房有序屏障:收到 Paused 後,伺服器即時房间的拆除仍可能短暫延遲。SDK 會從暫停確認時刻起建立 3 s settle 窗口;窗口內呼叫 resumeTravel 或 rewindTravel 時,suspend 呼叫先非阻塞等待剩餘時間,再傳送會重新開房的 API。若收到 Paused 後已自然等待滿 3 s,則不增加延遲。這樣可避免遲到的 pause teardown 關閉剛重開的房間並觸發 105001。

resume API 重試:resumeTravel 內部會在失敗時最多重試 3 次(退避 1 s / 2 s / 3 s),以應對暫停後服務短暫不可用的情況;只有 3 次全部失敗才會向上擲回錯誤。

說明建議宿主在 resumeTravel 返回成功後 3 s 內暫緩再次發起 pauseTravel,避免過於頻繁的切換。此為宿主端呼叫冷卻建議,SDK 本身不強制。詳見 HappyOyster Android SDK 整合指南。

簽章

suspend fun pauseTravel(): TravelStateData
suspend fun resumeTravel(): TravelStateData

參數

無參數。

傳回值

傳回 TravelStateData(包含 encryptedTravelId、status)。

錯誤

code

說明

103001

無活躍體驗

103002

狀態不允許,或該體驗不支援暫停/恢復

103003

在世界探索模式下呼叫,僅即時導演與角色演繹支援暫停/恢復

HappyOyster.rewindTravel

回溯至指定秒數。encryptedTravelId 由 SDK 內部自動取得。

前置條件:僅即時導演(directing)模式、且狀態為 paused 時可呼叫(不允許在 running 狀態下回溯);並非所有即時導演體驗都支援回溯,不支援時返回 103002。回溯成功後體驗會自動恢復,SDK 會自動重連 RTC,無需宿主干預。

角色演繹完全沒有回溯能力:在角色演繹下呼叫會被 SDK 在本機以 103003 拒絕,不發出任何請求。宿主在角色演繹下應隱藏回溯入口,而不僅是停用按鈕。

簽章

suspend fun rewindTravel(rewindToSec: Double): RewindTravelData

參數

欄位

類型

是否必填

描述

rewindToSec

Double

是

回溯到的目標秒數,應為 4 的整數倍(如 4、8、12)。非 4 的整數倍將由伺服器向下取整至最近的較小 4 倍數(如傳 7 則取 4)。

傳回值

傳回 RewindTravelData(包含 encryptedTravelId、status、resumedAtSec)。其中 resumedAtSec 為伺服器實際回溯到的秒數(已按 4 的整數倍向下取整)。

錯誤

code

說明

103001

無活躍體驗

103002

狀態不是 paused,或該體驗不支援回溯

103003

在世界探索或角色演繹模式下呼叫,僅即時導演模式支援回溯

HappyOyster.sendInstruct

傳送文字指令驅動劇情,僅在即時導演(directing)或角色演繹(acting)且體驗狀態為 running 或 paused 時有效。encryptedTravelId 由 SDK 內部自動取得。

paused 態行為:SDK 不會在 paused 時自動 resume,instruct 直接傳送。由宿主決定是否先呼叫 resumeTravel 再傳送 instruct。

簽章

suspend fun sendInstruct(content: String): SendInstructData

參數

欄位

類型

是否必填

描述

content

String

是

要傳送的文字指令內容。

傳回值

傳回 SendInstructData(包含 encryptedTravelId、content、accepted)。

錯誤

code

說明

103001

無活躍體驗(未呼叫 startTravel 或已結束)

103003

目前模式非即時導演或角色演繹(在世界探索模式下呼叫)

103002

體驗狀態既不是 running 也不是 paused(如尚在 init/pending 階段);或當前世界處於 ScriptList 模式(StartTravelData.creationModel == CreationModelValue.ScriptList)——ScriptList 模式下不允許傳送 instruct,劇本由百煉平台 API 管理

403004

內容安全審核攔截

404000

Travel 不存在

HappyOyster.sendCommand

傳送方向/視角/動作控制指令。僅在世界探索模式且狀態為 running 時有效。

  • 請在主執行緒呼叫;若離開主執行緒呼叫將同步拋出 IllegalStateException。
  • 無活躍體驗回報 103001。
  • 在非世界探索模式(即時導演/角色演繹)下呼叫會回報 103003。
  • 狀態不允許回報 103002。
  • 上行透過一路靜音音訊串流建立(SDK 不錄製、不上傳真實音訊);RECORD_AUDIO 並非 DataChannel 硬性前置條件,為相容各機型建議宣告並授予(見 HappyOyster Android SDK 整合指南 § 安裝 · 權限)。105004 表示即時通道未就緒 / 傳送失敗(如 DataChannel 連線中斷或即時通道異常),並非由缺少權限直接觸發。

內建 42 ms 節流(24fps,latest-wins):sendCommand 內部對 DataChannel 寫入做了以 42 ms(約 24fps)為週期的節流。狀態驗證同步、立即執行並在無效呼叫時立即擲回;DataChannel 實際寫入是非同步節流的:若距上次傳送已過 42 ms 則立即發出(首次立即發),否則將命令更新為最新值(latest-wins)並在當前 42 ms 週期結束時延遲發出一次。同一週期內多次呼叫等同於一次、以最後一次為準。這意味著宿主可以按遊戲影格率高頻呼叫 sendCommand,SDK 會穩定合併為約每 42 ms 一次上鏈,無需宿主手動限速。

節流期間的傳送失敗:節流 flush 是非同步的,傳送失敗無法擲回給呼叫端——錯誤會透過 onError 回呼(非嚴重錯誤,105004)透出。Session 結束時 pending 的命令會被丟棄(不會在結束後傳送)。

簽章

fun sendCommand(command: AdventureCommand)

參數

欄位

類型

是否必填

描述

command

AdventureCommand

是

包含 translation、rotation、interaction 三欄位的指令物件。

AdventureCommand 欄位與取值

translation — 移動

前/左/後/右/斜向/靜止。

值

方向

W

前

A

左

S

後

D

右

W_A

左前

W_D

右前

S_A

左後

S_D

右後

None

靜止

rotation — 視角

上/下/左/右/斜向/無。

值

方向

Mouse_Up

上

Mouse_Down

下

Mouse_Left

左

Mouse_Right

右

Mouse_Up_Left

左上

Mouse_Up_Right

右上

Mouse_Down_Left

左下

Mouse_Down_Right

右下

None

無

interaction — 互動

跳躍/攻擊/蹲下/衝刺/無。

值

動作

Jump

跳躍

Attack

攻擊

Squat

蹲下

Sprint

衝刺

None

無

三欄位彼此獨立,各自對應一組互斥指令;斜向移動/視角使用單一組合值(如同時前+左傳送 W_A,而非同欄位併發 W 與 A)。每次呼叫傳入當前完整狀態即可。

最佳實務:單次動作 vs 持續動作

以 42 ms 週期作為心智模型,區分兩類用法:

單次動作(如點擊一下跳躍/攻擊、走一步):呼叫一次即可。SDK 會在最近的週期將其上鏈,動作隨即生效;無需持續傳送,也無需補發 None。

// 跳一下
HappyOyster.sendCommand(AdventureCommand("None", "None", "Jump"))

持續動作(如按住持續移動、持續轉視角):按住期間按影格持續呼叫,SDK 每約 42 ms 上鏈一次,持續輸出該指令,角色即持續動作;鬆手時明確傳送一次帶 None 的指令復位停止。SDK 不會自動代發 None——不持續呼叫就沒有後續上鏈、動作會停下,但不發 None 就不會主動復位。

// 按住:每帧持续调用(宿主自行驱动帧循环)
HappyOyster.sendCommand(AdventureCommand("W", "None", "None"))
// 松手:显式复位一次
HappyOyster.sendCommand(AdventureCommand("None", "None", "None"))

傳回值

無傳回值(同步)。狀態驗證同步立即執行;DataChannel 寫入採用非同步節流。

錯誤

code

說明

103001

無活躍體驗

103002

狀態不允許

103003

在非世界探索模式(即時導演/角色演繹)下呼叫

105004(透過 onError)

節流 flush 傳送失敗(非致命,非同步回呼)

HappyOyster.attachVideo

傳回一個用於播放的 SurfaceView,由您加入版面配置;SDK 內部完成與遠端串流的渲染綁定。

  • 請在主執行緒呼叫;若離開主執行緒呼叫將同步拋出 IllegalStateException。
  • SDK 對傳回的 View 僅持有弱參考,體驗結束時釋放渲染綁定;您需自行將 View 從版面配置中移除。
  • 角色演繹體驗請先按 StartTravelData.aspectRatio 設定好播放容器方向,再將傳回的檢視掛入版面配置:渲染以裁剪填滿方式綁定,方向不符會裁掉畫面(見 startTravel)。

簽章

fun attachVideo(): SurfaceView

參數

無參數。

傳回值

傳回 SurfaceView,加入版面配置後即可播放即時視訊。

錯誤

code

說明

100001

SDK 未初始化

HappyOyster.endTravel

結束體驗。encryptedTravelId 由 SDK 內部自動取得。呼叫成功(或異常退出)後,SDK 自動斷開即時連線、停止內部輪詢、釋放全部工作階段資源,本次 ticket 同時失效。

簽章

suspend fun endTravel(): EndTravelData

參數

無參數。

傳回值

傳回 EndTravelData(包含 encryptedTravelId、status、endedAt、durationSec)。

錯誤

code

說明

100001

SDK 未初始化

103001

無活躍體驗

HappyOyster.VERSION

傳回 SDK 版本字串(SemVer),如 "x.y.z"。該值為編譯期常數,由 VERSION_NAME Gradle 屬性注入;無需先呼叫 initialize 即可安全讀取。

簽章

val VERSION: String

傳回值

SDK 版本字串,例如 "x.y.z"。

Log.d("MyApp", "SDK version: ${HappyOyster.VERSION}")

事件監聽

interface HappyOysterListener {
    fun onStatusChanged(status: TravelStatusValue) {}
    fun onError(error: SDKError) {}
}

fun addListener(listener: HappyOysterListener)
fun removeListener(listener: HappyOysterListener)

SDK 事件是對你的主動推送通道,用於回呼那些非你主動呼叫觸發的情況(例如 SDK 內部自動維護的即時連線或狀態輪詢出現問題)。

事件

說明

onStatusChanged

體驗狀態變更(包含即時視訊生命週期),值請參閱 TravelStatusValue。

onError

內部自動流程發生錯誤時回呼;致命錯誤會同時終止本次體驗(請參閱錯誤碼章節)。

說明addListener / removeListener 必須在 initialize 之後呼叫;在初始化前呼叫會擲回 SDKError(100001)。建議在 HappyOyster.initialize(...) 成功返回後立即註冊監聽器。

資料模型

// 配置
data class SDKConfig(
    // 必填:你账号的百炼 API Host(如 llm-xxxx.ap-southeast-1.maas.aliyuncs.com),百炼控制台 API Key 页复制;
    // 须与注入的 API Key 同账号/区域,否则网关返回 AccessDenied。
    val apiHost: String,
    // 必填且无默认值:包含版本的完整 HappyOyster 模型名称(如 happyoyster-1.0-directing);
    // 可用值以百炼官方 HappyOyster 系列模型文档为准。
    val model: String,
    // 内置 Logcat 输出的最低等级(不影响 logHandler);默认 INFO,含重建会话时间线所需的
    // 生命周期锚点(初始化、Travel 起止/状态、RTC 入会/首帧)。仅需报错可降为 WARN,排障可升到 DEBUG/VERBOSE。
    val logLevel: LogLevel = LogLevel.INFO,
    // RTC 入会(超时触发 105002)、首帧等待(超时触发 105003)与 SDK 网关 HTTP 信令调用(超时触发 105005)的超时;
    // 两个 RTC 阶段串行,最坏等待 2×callbackTimeoutMs(60s)。
    val callbackTimeoutMs: Long = SDKConfig.DEFAULT_CALLBACK_TIMEOUT_MS, // 30_000ms
    // SDK 是否将自身日志写入 Android Logcat(tag HappyOysterSDK);默认 false(静默)。
    val logcatEnabled: Boolean = false,
    // 宿主日志回调;接收全量 SDK LogRecord,不受 logLevel 影响;默认 null。
    val logHandler: HappyOysterLogHandler? = null,
) {
    companion object {
        /** 默认回调超时(毫秒)。公开常量,可用于对比或显示。 */
        const val DEFAULT_CALLBACK_TIMEOUT_MS: Long = 30_000
    }
}

enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR, NONE }

// 宿主日志接收器;通过 SDKConfig.logHandler 注入;全量 firehose,不受 logLevel 影响。
fun interface HappyOysterLogHandler {
    fun onLog(record: LogRecord)
}

// SDK 结构化日志记录;交付给 HappyOysterLogHandler;不含敏感值(Bearer / ticket / RTC token、
// RTC 标识与媒体 URL 均已脱敏)。travelId(encryptedTravelId)保留全值,可用于与服务端会话日志对账。
data class LogRecord(
    val level: LogLevel,
    val tag: String,        // 固定为 "HappyOysterSDK"
    val message: String,    // 可读日志行(含事件名与详情)
    val throwable: Throwable?,
    val timestampMs: Long,  // 发出时的 epoch 毫秒
)

// 状态与模式(保留未知值,便于服务扩展)
@JvmInline value class TravelStatusValue(val rawValue: String) {
    companion object {
        val Init = TravelStatusValue("init")
        val Pending = TravelStatusValue("pending")
        val Running = TravelStatusValue("running")
        val Paused = TravelStatusValue("paused")
        val Failed = TravelStatusValue("failed")
        val Completed = TravelStatusValue("completed")
    }
}
@JvmInline value class ModeValue(val rawValue: String) {
    companion object {
        val Adventure = ModeValue("adventure")
        val Directing = ModeValue("directing")
        val Acting = ModeValue("acting")
    }
}
@JvmInline value class CreationModelValue(val rawValue: String) {
    companion object {
        val Simple = CreationModelValue("simple")         // 默认;prompt/instruct 驱动,世界探索类世界归一化为此值
        val ScriptList = CreationModelValue("scriptlist") // 结构化 ScriptList 世界;不支持 sendInstruct(抛 103002)
    }
}

// startTravel 返回
data class StartTravelData(
    val encryptedTravelId: String, // 后续控制接口的标识
    val encryptedWorldId: String,
    val mode: ModeValue,           // adventure / directing / acting
    val creationModel: CreationModelValue = CreationModelValue.Simple, // 世界创建模型;Simple(默认)为 instruct 驱动,ScriptList 不支持 sendInstruct
    val playUrl: String?,
    val firstFrame: String?,       // 首帧图地址,可能异步产生
    val bgmUrl: String?,
    val version: String,           // 世界版本标识;角色演绎的 version 为 actingV2
    val aspectRatio: String? = null, // 仅角色演绎返回 9:16 / 16:9;其余模式 null。用于定播放器方向
    val maxExperienceTimeSec: Int? = null, // 服务端回显的本次最大体验时长(秒);仅 adventure 非 null,directing / acting 为 null
)

// 控制接口参数与返回
data class AdventureCommand(
    val translation: String, // 移动:前/左/后/右/斜向(W_A 等)/静止
    val rotation: String,    // 视角:上/下/左/右/斜向(Mouse_Up_Left 等)/无
    val interaction: String, // 交互:跳跃/攻击/蹲下/冲刺/无
)
data class TravelStateData(val encryptedTravelId: String, val status: TravelStatusValue)
data class RewindTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val resumedAtSec: Double)
data class EndTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val endedAt: String, val durationSec: Int)
data class SendInstructData(val encryptedTravelId: String, val content: String, val accepted: Boolean)

// 错误(以 code 标识,原始信息见 raw)
// 注意:SDKError 不是 data class(无 copy()/解构),是普通 class。
class SDKError(val code: Int, val raw: Any? = null) : Exception("Happy Oyster SDK error: $code")

錯誤碼

錯誤以數字 code 識別。SDK 會透傳可由呼叫端處理的業務錯誤碼(常見為 4xxxxx / 5xxxxx),本機 SDK 錯誤碼為 1xxxxx。

業務錯誤碼(常見)

code

含義

建議處理方式

400000

參數非法(列舉非法等)

檢查請求參數或 SDK 版本

401010

ticket 無效或已過期

讓伺服器端重新下發憑證

401011

ticket 已被使用

憑證一次性,重新下發

403001

World 不存在、已刪除或不屬於目前開發者(包含 startTravel 憑證內的 World 已刪除)

重新選擇有效 World

403002

世界狀態非就緒

等世界就緒後再開始

403004

輸入內容違規(內容安全審查);適用於 sendInstruct 文字指令

修改輸入內容後重試

403007

目前規格未開通

不可按容量滿重試;請更換已開通規格或聯絡開通

403008

容量配置暫不可用

稍後重試

404000

Travel 資源不存在或 ID 不屬於目前帳號

重新開始 Travel

409000

請求與目前資源狀態衝突

檢查 Travel 狀態

429001

目前規格併發已滿

待現有工作階段結束後重試(請勿與 500001 混淆)

429002

目前可用容量不足

稍後重試

500001

推理資源分配/服務內部失敗

稍後重試

500000

系統內部錯誤

稍後重試 / 回饋

用戶端本機錯誤碼

說明「是否為致命錯誤」專指是否觸發 SDK 自動終止本次體驗:

  • 100001 / 100002 / 103xxx 這類同步驗證錯誤只會拒絕/擲回該次呼叫(100002 會直接導致初始化失敗),不會終止體驗。
  • SDK 自動終止本次體驗分為四類:① 內部輪詢讀到 failed;② 即時連線嚴重錯誤(105001 / 105002 / 105003);③ 無推流自動結束(105006);④ SDK 被遠端停用(108001)。

code

含義

是否為致命錯誤

建議處理方式

100001

SDK 未初始化即呼叫

呼叫拒絕

先 initialize

100002

SDK 初始化時 model 為空白;同步擲回,SDKError.raw 為 SDKConfig.model must not be blank,不會建立或替換 runtime,也不會發起網路請求

初始化失敗

傳入非空白的完整模型名稱及版本後重新 initialize;可用值以百煉官方 HappyOyster 系列模型文件為準

101001

未注入百煉閘道 API Key

否

updateToken 後重試

101002

百煉閘道 Bearer API Key 已過期或被拒絕。注意:這是閘道 Bearer Key,不是一次性 ticket;ticket 層級的憑證錯誤由六位數伺服器 code 識別(如 401010/401011)

否

重新取得 Bearer Key 後執行 updateToken,無需重新換取 ticket

103001

目前無活躍體驗

呼叫拒絕

先 startTravel

103002

當前狀態不允許該操作(包含:狀態不符、pauseTravel/resumeTravel/rewindTravel 的體驗不支援暫停 / 恢復 / 回溯(version 不是本模式要求的 v2 識別碼)、rewindTravel 時狀態非 paused、sendInstruct 時 creationModel == ScriptList)

呼叫拒絕

檢查體驗狀態與模式;ScriptList 模式(creationModel == CreationModelValue.ScriptList)下不可使用 sendInstruct,劇本內容不透過本 SDK 管理

103003

模式不匹配:在即時導演或角色演繹下呼叫了 sendCommand,或在世界探索模式下呼叫了 pauseTravel / resumeTravel / rewindTravel / sendInstruct,或在角色演繹下呼叫了 rewindTravel

呼叫拒絕

檢查目前模式是否符合介面要求

103004

併發呼叫 startTravel,或 Travel 正在 starting、active、ending 時重新初始化(同一時刻僅允許一個 Travel;併發 start 的 SDKError.raw 僅含 ticket 去識別化摘要)

呼叫拒絕

等待 endTravel() 後重試

105001

即時連線失敗

是

結束並重新開始

105002

即時入會逾時

是

結束並重新開始

105003

等待視訊首幀逾時

是

結束並重新開始

105004

即時通道未就緒/傳送失敗(例如 DataChannel 連線中斷或即時通道異常)

否

確認即時通道已就緒,於 running 後重試 sendCommand;若個別機型異常,可嘗試宣告並授予 RECORD_AUDIO(見 HappyOyster Android SDK 整合指南 § 安裝 · 權限)

105005

SDK 閘道 HTTP 信令呼叫在 callbackTimeoutMs 內未返回(預設 30s);RTC 加入與首影格等待逾時分別使用 105002 / 105003

否

建議重試,或調大 callbackTimeoutMs

105006

無推流自動結束:首訊框從未送達,或執行中推流中斷且逾時未恢復。SDK 會主動結束本次體驗

是

結束並重新開始

106001

本機網路錯誤

否

可重試

106002

回應解析失敗

否

使用者主動呼叫時拋給呼叫方;內部狀態輪詢時僅觸發 onError,不會終止體驗

106003

上游服務傳回無法識別的錯誤回應,SDK 已將原始資訊保留在 SDKError.raw(包含閘道邊緣拒絕,如 AccessDenied)

否

可重試;若持續出現,請結合 raw 排查服務可用性。若 raw 包含 AccessDenied(多見於初始化後首次請求),屬閘道設定問題,請依序排查:① model 名稱與版本是否正確、是否已下線、是否已發佈及授權;② apiHost 拼寫;③ apiHost、model 與 updateToken 注入的 Key 是否屬於匹配的帳號和區域;④ 帳號是否已加入應用程式白名單

108001

SDK 被遠端停用(整體關閉或版本過低);原因位於 SDKError.raw(String)

是(SDK 偵測到停用結果時會自動結束進行中的體驗;恢復後可重新開始)

依據 raw 中的原因提示使用者;版本過低時引導升級

致命 vs 非致命

嚴重錯誤:SDK 會自動終止本次體驗(斷開即時連線、釋放資源、呼叫 endTravel),並透過 onError 透出;宿主應清理本次體驗狀態並允許重新開始。共四類:

  • ① 內部狀態輪詢讀取到體驗狀態為 failed(例如 500001 推理失敗)
  • ② 即時連線發生致命錯誤(105001/105002/105003)
  • ③ 無推流自動結束(105006:首訊框從未送達,或執行中推流中斷且逾時未恢復,SDK 主動結束)
  • ④ SDK 被遠端停用(108001)

非嚴重錯誤:不終止體驗,僅透過 onError 透出;您可重新 updateToken 或等待服務 / 即時通道恢復後繼續。內部狀態輪詢自身的單次請求失敗(網路 106001、解析 106002、上游異常 106003、業務錯誤 5xxxxx)屬於此類——輪詢會在下一週期繼續,只有它讀到狀態 failed 才會結束體驗;驗證失敗 101001、sendCommand 節流 flush 的非同步傳送失敗 105004 同樣為非嚴重錯誤。註:SDK 不會自行向即時通道傳送任何保活訊息,因此空閒期不會出現 105004——該錯誤只可能由您主動呼叫 sendCommand 觸發。