HappyOyster iOS SDK 入口為處理程序級單例 HappyOysterEngine.shared,業務方法均為 async throws,失敗時拋出 OysterSDKError。一次體驗由 OysterTravel 控制代碼承載,支援 UIKit 與 SwiftUI。
本文檔是 HappyOyster iOS SDK 的對外功能描述與介面細節參考:逐一說明公開類型與方法的參數、使用時機、用法與簡單範例。
完整接入流程(工程建置、相依性設定、伺服器端配合、端到端跑通)請參閱 HappyOyster iOS SDK 接入指南,本文不再贅述。
核心概念
先確立幾個名詞,以便閱讀後文。
概念 | 說明 |
|---|---|
token | HTTP 驗證 token(百煉臨時 API Key)。由您的伺服器呼叫百煉介面換取後下發,經 |
ticket | 一次性體驗憑證。由您的伺服器呼叫開放平台 |
World | AI 世界,含角色、場景。由您的伺服器建立與管理,SDK 不涉及。 |
Travel | 一次即時體驗對應一個 |
工作階段狀態 |
|
模式 |
|
概覽
整合本 SDK 後,您的 App 可進入由 AI 即時生成的「世界」,以世界探索模式(adventure)、即時導演模式(directing)或角色演繹模式(acting)進行即時互動視訊體驗:開始體驗 → 即時播放 → 即時互動 → 過程控制(暫停/恢復/回溯/結束)→ 狀態與錯誤回調。
各模式可用的能力不同,請按 start() 傳回的 mode 決定 UI:
能力 | adventure | directing | acting |
|---|---|---|---|
| 支援 | 不支援 | 不支援 |
| 不支援 | 支援 | 支援 |
| 不支援 | 支援 | 支援 |
| 不支援 | 支援 | 不支援,請隱藏入口 |
| 支援 | 支援 | 支援 |
| 生效 | 忽略 | 忽略 |
說明acting 世界以直向螢幕優先:start() 返回的 aspectRatio 提供本次體驗的畫幅比例(9:16 / 16:9),建議在拉流前據此決定播放器方向與容器尺寸(詳見「資料模型」章節)。
import HappyOysterSDK,核心入口是兩個類型。
HappyOysterEngine —— 編排入口,處理程序級單例 HappyOysterEngine.shared。
方法 / 屬性 | 說明 |
|---|---|
| 初始化執行階段並自動註冊即時引擎( |
| 注入 / 更新 HTTP 驗證 token |
| 使用一次性憑證建立工作階段控制代碼 |
| 釋放資源(可再 |
| 目前 SDK 版本號 |
OysterTravel —— 單次體驗的工作階段控制代碼,由 createTravel(ticket:) 建立。
方法 / 屬性 | 說明 |
|---|---|
| 播放檢視(UIKit / SwiftUI) |
| 狀態變更 + 錯誤推送串流 / 可觀察的目前狀態 |
| 建立連線播放(可選請求世界探索模式體驗的最大時長) |
| 即時導演模式文字指令 |
| 世界探索模式操控 |
| 暫停 / 恢復( |
| 回溯(僅限 paused) |
| 結束(冪等,務必呼叫) |
| 暫時釋放 / 恢復麥克風 |
此外還有幾個輔助匯出:OysterLog 用於接管 SDK 內部日誌;HappyOysterEngine.version 讀取版本號;OysterVideoView 是 SwiftUI 播放檢視(與 videoView 等效)。用法詳見後文。
生命週期:初始化 → 注入 token → 建立工作階段 → 掛載視訊與訂閱事件 → start 播放 → 互動 → end。SDK 不負責世界的建立與管理(由您的伺服器完成),也不公開底層即時通訊細節。
快速上手
一段從初始化到結束的完整流程,逐步註解。各 API 的細節見「API 參考」章節。
import HappyOysterSDK
// 1) 初始化(App 启动后尽早,createTravel 前一次)
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
apiHost: "[workspace-id].[region].maas.aliyuncs.com",// 网关地址获取参考百炼文档
model: "happyoyster-1.0-adventure" // 已开通的模型名(含版本),必填;取值见 HappyOyster 系列模型文档
))
// 2)(可选)接管日志:设置等级 + 自定义打印
OysterLog.setMinimumLevel(.info)
OysterLog.setHandler { level, tag, message in
print("[Oyster][\(level)][\(tag)] \(message)")
}
// 3) 注入 HTTP 鉴权 token(百炼临时 API Key,由你的服务端下发)
engine.updateToken(temporaryApiKey)
// 4) 用一次性 ticket 创建会话句柄(尚未建连)
let travel = try engine.createTravel(ticket: ticket)
Task { @MainActor in
// 5) 挂载视频(UIKit;SwiftUI 用 OysterVideoView(travel:))
containerView.addSubview(travel.videoView)
// 6) start 前就订阅事件,避免漏掉早期状态
let eventTask = Task {
for await event in travel.events {
switch event {
case .statusChanged(let status): render(status) // 见"事件与状态"章节
case .error(let error): handle(error) // 见"错误码"章节 error.code / error.kind
}
}
}
do {
// 7) 建连播放
let data = try await travel.start()
// 8) 按模式互动:adventure 走操控指令,directing / acting 走文本指令
if data.mode == .adventure {
travel.sendCommand(OysterAdventureCommand(translation: .front))
} else {
_ = try await travel.sendInstruct(content: "突然下起了大雨")
}
} catch let error as OysterSDKError {
handle(error)
}
// 9) 任意退出路径都收口到一次 end()
_ = try? await travel.end()
eventTask.cancel()
}
// 退出 SDK / 切换网关:await engine.cleanup()
說明工程建置、相依性設定(含必須引入的 AliRTC 適配器與廠商二進位檔)、伺服器配合與端到端跑通,請參閱範例工程文件。
環境需求
項目 | 要求 |
|---|---|
最低系統版本 | iOS 15.0+(公開類型均標記 |
語言 | Swift( |
並行處理 | 主執行緒存取(入口類型標記 |
網路 | 需可存取公網 |
權限 |
|
麥克風權限:即時互動視訊體驗需要建立一條雙向(上行 + 下行)即時影音通道,因此 SDK 運行期間會佔用本機麥克風——這並非錄音。Info.plist 必須提供 NSMicrophoneUsageDescription,否則啟動即時採集時會崩潰。當您需要獨佔麥克風(如語音辨識)時,請使用 pauseLocalAudioCapture() / resumeLocalAudioCapture() 暫時釋放與恢復(詳見「OysterTravel」章節)。
引入與驗證
引入與相依性
程式碼層面 import HappyOysterSDK 即可。SDK 以預編譯二進位檔(xcframework)按 CocoaPods subspec 分發,已發佈至 CocoaPods 公開 Trunk——請在 Podfile 中宣告相依性:
# HappyOysterSDK / AliVCSDK_ARTC 均发布在 CocoaPods 公开源。
pod 'HappyOysterSDK', '1.0.3' # 聚合入口(Core + World)
pod 'HappyOysterSDK/UI', '1.0.3' # 可选:默认 UI 组件(视频视图、操控 HUD)
pod 'HappyOysterSDK/StreamAliRTC', '1.0.3' # 视频流 + AliRTC 引擎适配器(已依赖 Stream)
# RTC 厂商二进制:SDK 弱引用、不随 SDK 分发,由集成方自行引入。
pod 'AliVCSDK_ARTC', '7.11.0'
說明引入 HappyOysterSDK/StreamAliRTC 時 AliVCSDK_ARTC 為必要項目:若缺失,SDK 會靜默回落 Loopback——能連上且狀態走到 running,但會黑屏且不報錯。
工程建置與端到端初始化流程由範例工程負責,請參閱範例工程文件。本文聚焦於介面本身。
驗證模型
SDK 不獲取、不重新整理 token,保持輕量。驗證分為兩層,整合方負責生命週期管理:
- HTTP 驗證 token(百煉臨時 API Key):由您的伺服器呼叫百煉介面換取後下發,經
updateToken(_:)注入。SDK 內部部分服務直接呼叫百煉閘道,並攜帶該 token 進行驗證——因此它必須是百煉端簽發的臨時 API Key,而非您自有業務服務的 token。SDK 僅保存最新一個,不持久化、不重新整理;過期後由您重新換取並再次注入。 - 一次性體驗憑證
ticket:由您的伺服器呼叫開放平台get-travel-credential換取(前綴tk_,有效期 30 分鐘,一次性),作為createTravel(ticket:)的輸入參數;體驗結束(正常或異常)或過期後即失效,不可重複使用。
說明AK、簽名金鑰等高權限僅存在於您的伺服器,用戶端 SDK 永不接觸;用戶端取得的始終是短時效臨時 API Key。
說明Feature Gate(遠端開關 / 強制升級):伺服器可遠端停用 SDK 或設定最低支援版本。被停用時,受控呼叫(start / pause / resume / rewind / sendInstruct / sendCommand)會被拒絕並透出 108001,進行中的體驗會被 SDK 主動終止(詳見「錯誤碼」章節);OysterSDKError.raw 中帶有可讀的停用原因,版本過低時請引導使用者升級。
處理 token 過期:上述 HTTP 驗證 token 過期後,需立即向您的伺服器重新請求 token,再透過 updateToken(_:) 注入。有兩處需判斷 token 是否過期:
- 呼叫
engine.createTravel、travel.start等 API 時,處理 error,判斷 token 過期或無效的 error 類型(101001/101002),注入新 token 後重新呼叫對應 API。 - 監聽
OysterTravelEvent的.error事件時,判斷 token 過期或無效的 error 類型,重新請求 token 並注入。
API 參考
入口為兩個類型,均標註 @MainActor、@available(iOS 15.0, *)。業務方法為 async throws,失敗時拋出 OysterSDKError;有返回值的方法均標註 @discardableResult。
HappyOysterEngine
處理程序級單例,編排入口。init 非 public——請統一使用 HappyOysterEngine.shared,切勿自行實體化(底層即時引擎亦為處理程序單例)。
@MainActor @available(iOS 15.0, *)
public final class HappyOysterEngine {
public static let shared: HappyOysterEngine // 进程级唯一实例
public static let version: String // 当前 SDK 版本号
@discardableResult
public func initialize(config: OysterConfig) -> Bool // 初始化(createTravel 前一次)
public func updateToken(_ token: String) // 注入/更新 HTTP 鉴权 token
public func createTravel(ticket: String) throws -> OysterTravel // 用一次性凭证创建会话句柄
public func cleanup() async // 释放资源(可再 initialize)
}
initialize(config:)
初始化執行階段並自動註冊即時引擎(無需宿主手動註冊)。
使用時機:createTravel 前呼叫一次,App 啟動後盡早呼叫。
注意:空閒時再次呼叫即以新 config 重新裝配(更換 apiHost / model 都不必先 cleanup(),已注入的 token 會保留);僅當有進行中的 Travel 時為 no-op 並發出警告,需先 end()。config 無效時維持現狀,已生效的運行環境不受影響。
簽章
@discardableResult
public func initialize(config: OysterConfig) -> Bool
傳回值
Bool —— 本次傳入的 config 是否已生效。false 有兩種情況:config 無效(apiHost / model 為空或無法組合出合法閘道 URL),或有進行中的 Travel 導致本次呼叫被忽略;這兩種情況下運行環境皆維持原狀。
說明更換 config 時請勿使用 isReady 判斷成敗:若重新 initialize 被拒,先前的 config 仍在生效,isReady 依舊為 true。isReady 回答的是「引擎現在可用嗎」,而本返回值回答的是「我剛傳的這份 config 生效了嗎」。
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 是 |
|
說明模型與 mode 一一對應:按模式拆分後,每個模型是一條獨立的閘道路由,一次 initialize 僅服務一種 mode 的世界。若您的 App 同時提供多種模式的世界,在進入不同模式的世界前使用對應模型再 initialize() 一次即可——空閒時以最新 config 為準,不需要 cleanup(),已注入的 token 也會保留;若有進行中的 Travel,該呼叫會被忽略,需先 end()。當模型與世界 mode 不匹配時,閘道會以 AccessDenied 拒絕並歸一為 106003。
OysterLog
設定並接管 SDK 內部的日誌,輸出至您自己的日誌模組。提供 setMinimumLevel(_:) 設定等級、setHandler(_:) 自訂列印(詳見「快速入門」範例)。
updateToken(_:)
注入 / 更新 HTTP 驗證 token(百煉臨時 API Key,見「驗證模型」章節)。
使用時機:initialize 之後可隨時呼叫;token 過期或收到驗證類錯誤(101001 / 101002)後重新換取並再次呼叫。
注意:未 initialize 時為 no-op 並發出警告。
簽章
public func updateToken(_ token: String)
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 是 | HTTP 驗證 token(百煉臨時 API Key,見「驗證模型」章節)。 |
createTravel(ticket:)
使用一次性 ticket 建立一次工作階段控制代碼。
使用時機:每次開始新體驗前呼叫;返回的控制代碼尚未建立連線,需再呼叫 travel.start()。視訊畫面從返回的控制代碼取得(詳見「OysterTravel」章節)。
注意(同步 throws):未 initialize 時拋出 100001;上一個 Travel 未 end() 前再次呼叫則拋出 103004(每個 engine 同時僅允許一個 active Travel)。
簽章
public func createTravel(ticket: String) throws -> OysterTravel
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 是 | 一次性憑證,建立後即視為本次體驗佔用。 |
傳回值
返回 OysterTravel 工作階段控制代碼;尚未建立連線,需再呼叫 start()。
錯誤
code | 說明 |
|---|---|
| 未 |
| 上一個 Travel 未 |
cleanup()
釋放 SDK 資源(結束 active travel、執行階段設定、token)。
使用時機:徹底退出 SDK 或需要更換 config 時。
注意:async——內部會先確定性地 end() 目前的 active travel,再拆解運行環境,不留 fire-and-forget。釋放後可再次 initialize。
簽章
public func cleanup() async
OysterTravel
由 createTravel 建立的工作階段控制代碼;僅限單次使用,到達終止狀態(end / 伺服器端結束 / 失敗)後即失效,需透過 engine 重新 createTravel。同時也是 ObservableObject(@Published status,可直接驅動 SwiftUI)。
@MainActor @available(iOS 15.0, *)
public final class OysterTravel: ObservableObject {
@Published public private(set) var status: OysterTravelStatus // 当前对外状态(可观察)
public var isEnded: Bool { get } // 是否已到达终态(同步可读)
public var videoView: UIView { get } // UIKit 播放视图;SwiftUI 用 OysterVideoView(travel:)
public var events: AsyncStream<OysterTravelEvent> { get } // 状态变更 + 错误,多订阅
@discardableResult public func start() async throws -> OysterStartTravelData // 建连播放
@discardableResult public func start(maxExperienceTimeSec: Int?) async throws -> OysterStartTravelData // 建连播放 + 请求最大体验时长(仅世界探索模式)
@discardableResult public func pause() async throws -> OysterTravelStateData // 暂停(directing / acting)
@discardableResult public func resume() async throws -> OysterTravelStateData // 恢复
@discardableResult public func rewind(toSec: TimeInterval) async throws -> OysterRewindTravelData // 回溯(仅 paused)
@discardableResult public func end() async throws -> OysterEndTravelData // 结束(幂等,务必调用)
@discardableResult public func sendInstruct(content: String) async throws -> OysterSendInstructData // 实时导演模式文本指令
public func sendCommand(_ command: OysterAdventureCommand) // 世界探索模式操控(fire-and-forget)
public func flushCommands() // 松键时补发最后一帧
public func pauseLocalAudioCapture() async // 临时让出麦克风
public func resumeLocalAudioCapture() async // 恢复麦克风占用
}
videoView / OysterVideoView(travel:)
遠端畫面渲染入口,「SDK 提供檢視、宿主負責擺放」。UIKit 取 travel.videoView;SwiftUI 使用 OysterVideoView(travel:)。
使用時機:建立控制代碼後即可取得(多次存取返回同一檢視),掛載至任意層級,引擎就緒後自動渲染,start() 前後掛載均可,不會黑屏。
注意:工作階段結束時 SDK 會自動釋放渲染繫結,您可按需將檢視移出層級。
簽章
public var videoView: UIView { get }
events / status / isEnded
events 是狀態變更與錯誤的推送串流;status 是可觀察的目前對外狀態;isEnded 可同步讀取是否為終止狀態。
使用時機:建議在 start() 之前就開始消費 events,避免遺漏早期狀態。
注意:每次存取 events 會返回一條獨立串流,支援多重訂閱;取消訂閱等同於結束 for await 迭代(或銷毀持有的 Task)。SwiftUI 可直接使用 @StateObject/@ObservedObject 觀察 status(錯誤仍走 events)。詳見「事件與狀態」章節。
簽章
@Published public private(set) var status: OysterTravelStatus
public var isEnded: Bool { get }
public var events: AsyncStream<OysterTravelEvent> { get }
start() / start(maxExperienceTimeSec:)
使用 create 時捕獲的 ticket 換取 travel 與 RTC 入會設定並建立連線播放。成功後 SDK 會自動建立即時連線並開始內部狀態輪詢,狀態經 events 透出。
注意(無推流自動結束):伺服器下發「無推流逾時」(預設約 30s)。若建立連線後在該時長內仍未收到推流(遲遲未進入 running),SDK 會自動結束本次體驗,狀態轉為 failed、經 events 的 .error 透出 105006(致命,請按回到開始前介面處理,無需自行計時)。
簽章
@discardableResult public func start() async throws -> OysterStartTravelData
@discardableResult public func start(maxExperienceTimeSec: Int?) async throws -> OysterStartTravelData
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 否 | (選用)——請求本次世界探索(adventure)體驗的最大持續時長(秒)。參數會原樣傳送給伺服器,允許值、實際生效時長與到期自動結束時機均由伺服器決定,SDK 不做本機驗證;傳 |
傳回值
OysterStartTravelData(mode / version / encryptedTravelId 等,詳見「資料模型」章節),據此決定互動 UI。
說明ticket 的世界 mode 必須與 initialize 傳入的 model 匹配(由呼叫方保證):模型按 mode 拆分後,每個模型是一條獨立的閘道路由,start() 是將 ticket 傳往目前模型那條路由。SDK 不會也無法在呼叫前替您驗證這一點——mode 由本次 start() 的回應下發(即 OysterStartTravelData.mode),呼叫前 SDK 手中只有不透明的 ticket 和模型名稱,沒有可比對的 mode;而從模型名稱反推 mode 屬於對伺服器命名的猜測,SDK 不會執行此操作。
因此:切換至另一種 mode 的世界之前,請使用對應模型再 initialize() 一次(空閒時以最新 config 為準,無需 cleanup(),token 會保留;若有進行中的 Travel,該呼叫會被忽略,需先 end())。若不匹配,本次 start() 會在閘道端失敗,排查時請優先核對「目前 model 與本次 ticket 所屬世界的 mode 是否成套」,再查看下表的憑證類錯誤碼。
錯誤
code | 說明 |
|---|---|
| 憑證無效(亦包括:憑證有效,但所屬世界的 |
| 憑證已使用 |
| 世界未就緒 |
| 目前規格未開通(如角色演繹(acting)規格) |
| 併發已滿 / 可用容量不足 |
| 資源/服務端失敗 |
| 並行啟動 |
pause() / resume()
暫停 / 恢復體驗(冪等)。
使用時機:directing(即時導演)與 acting(角色演繹)世界支援,adventure 不支援。可使用 start() 返回的 mode 提前決定是否顯示暫停按鈕。pause 要求目前為 running;resume 要求為 paused。
簽章
@discardableResult public func pause() async throws -> OysterTravelStateData
@discardableResult public func resume() async throws -> OysterTravelStateData
返回值
返回 OysterTravelStateData(欄位見「数据模型」章節)。
錯誤
code | 說明 |
|---|---|
| 無活躍體驗 |
| 狀態/版本不允許 |
| 模式不匹配 |
rewind(toSec:)
回溯至指定秒數。成功後 SDK 會自動使用原始 rtcConfig 重新加入會議並回到播放。
使用時機:僅 paused 狀態可發起,且只有 directing(即時導演)世界支援——acting 與 adventure 均不支援回溯,請在這兩種模式下隱藏回溯入口,切勿呼叫。
簽章
@discardableResult public func rewind(toSec: TimeInterval) async throws -> OysterRewindTravelData
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 是 | 回溯到的目標秒數。 |
返回值
返回 OysterRewindTravelData(欄位見「数据模型」章節)。
錯誤
code | 說明 |
|---|---|
| 無活躍體驗 |
| 狀態不允許 |
end()
結束體驗(具冪等性,可重複呼叫)。呼叫成功或異常退出後,SDK 會自動斷開即時連線、停止輪詢並釋放所有工作階段資源,ticket 同時失效,控制代碼進入終止狀態。
使用時機與注意事項:無論使用者主動退出或被動結束(計時到期、收到 .ended/.failed、頁面銷毀),都必須確保執行一次 end(),否則遠端資源釋放可能不及時。建議將所有退出路徑收斂至同一個冪等清理方法。
簽章
@discardableResult public func end() async throws -> OysterEndTravelData
返回值
返回 OysterEndTravelData(欄位見「数据模型」章節)。
sendInstruct(content:)(即時導演模式)
傳送文字指令驅動劇情。
使用時機:即時導演模式;running 時直接傳送,paused 時暫存、待 resume 重新連入 running 後隨首幀補傳。
簽章
@discardableResult public func sendInstruct(content: String) async throws -> OysterSendInstructData
參數
欄位 | 類型 | 是否必填 | 描述 |
|---|---|---|---|
|
| 是 | 要傳送的文字指令內容。 |
返回值
返回 OysterSendInstructData(欄位見「数据模型」章節)。
錯誤
code | 說明 |
|---|---|
| 無活躍體驗 |
| 狀態不允許 |
| 在世界探索模式下呼叫 |
| 輸入內容違規(內容安全審查) |
| Travel 不存在 |
sendCommand(_:) / flushCommands()(世界探索模式)
sendCommand:傳送方向/視角/動作控制指令(詳見「資料模型」章節OysterAdventureCommand,fire-and-forget,無返回值、不 throws)。僅於世界探索模式且為running時有效。外部可每幀高頻持續輸入,SDK 內部會進行節流(latest-wins 取樣、按 RTC 線速合幀),宿主無需自行節流。請注意伺服器對指令的回應本身有延遲,實際生效時間並不固定。flushCommands:於「放開按鍵 / 輸入釋放」瞬間呼叫,立即補傳佇列中已等待的最後一條指令;若無待傳指令則為純 no-op,不會產生新指令。
簽章
public func sendCommand(_ command: OysterAdventureCommand)
public func flushCommands()
錯誤
code | 說明 |
|---|---|
| 無活躍體驗 |
| 在即時導演模式下呼叫 |
| 即時通道未就緒/傳送失敗 |
(均透過 events 的 .error 透出,非 throws。)
pauseLocalAudioCapture() / resumeLocalAudioCapture()
暫時釋放 / 恢復 SDK 對本機麥克風採集的佔用。
使用時機:語音辨識等需要獨佔麥克風時先 pause,結束後 resume。
簽章
public func pauseLocalAudioCapture() async
public func resumeLocalAudioCapture() async
事件與狀態
事件掛在 OysterTravel.events 上,是 SDK 對您的主動推送通道,用於透出非您主動呼叫所觸發的情況(例如內部自動維護的即時連線或狀態輪詢出現問題)。
var events: AsyncStream<OysterTravelEvent> { get }
@available(iOS 15.0, *)
public enum OysterTravelEvent {
case statusChanged(OysterTravelStatus)
// SDK 内部流程出错,例如内部接口请求出错、推流出错,注意这里必须判断 token 过期并重新请求 token
case error(OysterSDKError)
}
消費方式二選一:
- SwiftUI:
OysterTravel是ObservableObject,可直接透過@StateObject/@ObservedObject觀察status驅動 UI;錯誤仍從events取得。 - 命令式 / UIKit:在
Task中使用for await event in travel.events { ... },並以switch處理.statusChanged/.error;結束時取消持有的Task。
let task = Task {
for await event in travel.events {
switch event {
case .statusChanged(let status): render(status) // 见下表
case .error(let error): handle(error) // error.code / error.kind,见"错误码"章节
}
}
}
// 结束时:task.cancel()
OysterTravelStatus(5 個過程態 + 2 個終態):
狀態 | 說明 | 典型處理 |
|---|---|---|
| create 後、start 前 | — |
| 建立連線 / 重新連線中(內部 connecting / reconnecting) | 顯示連線 / 重新連線提示 |
| 串流已就緒、可互動(內部 playing) | 顯示畫面與操控 |
| 暫停已受理、等待伺服器確認 | 顯示「暫停中…」 |
| 已暫停(確認) | 顯示暫停狀態(僅 |
| 已結束(主動 end 或伺服器端結束)。終態 | 收尾並关闭頁面 |
| 失敗。終態 | 顯示錯誤並收尾 |
說明進入 ended / failed 後工作階段即終止,所有工作階段操作(pause/resume/sendCommand…)均不再生效。回調可能在主執行緒觸發,可直接用於更新 UI。
說明SDK 內部狀態輪詢時會自動上報用戶端拉流與播放狀態心跳(connecting / playing / paused / reconnecting / disconnected),供伺服器區分推流端與用戶端狀態——此為純 SDK 內部行為,宿主無需感知或參與。
資料模型
說明所有公開類型均標註 @available(iOS 15.0, *)。下列返回值與參數為 SDK 輸出參數,由內部就地建構並使用 Swift 原生類型(Date / TimeInterval / OysterTravelStatus),並非 Codable、也不公開 wire(snake_case)細節——wire 解碼發生在 SDK 內部。
// 配置
public struct OysterConfig: Sendable {
public let apiHost: String // 百炼网关地址(非业务 Server),必填、由宿主显式传入
public let model: String // 模型名含版本(如 "happyoyster-1.0-adventure"),必填、无默认值
public let logLevel: OysterLogLevel? // .debug/.info/.warning/.error;nil 时默认 .warning
public let callbackTimeoutMs: Int // 全局回调超时,默认 30000;超时按 105005 透出
public init(apiHost: String, model: String, logLevel: OysterLogLevel? = nil, callbackTimeoutMs: Int = 30_000)
}
public enum OysterLogLevel: Int, Comparable, CaseIterable, Sendable {
case debug, info, warning, error
}
// 对外会话状态(见"事件与状态"章节)
public enum OysterTravelStatus: String, Equatable, Sendable, CustomStringConvertible {
case idle // create 后、start 前
case prepare // 建连 / 重连中
case running // 流已就绪、可交互
case pausing // 暂停已受理、等待服务端确认
case paused // 已暂停(确认)
case ended // 终态:主动 end 或服务端结束
case failed // 终态:失败
}
// 开放字符串值(保留未知值,便于服务端扩展;编解码为裸 JSON 字符串 "running")
public struct OysterRawValue: RawRepresentable, Equatable, Hashable, Codable, Sendable {
public let rawValue: String
public init(rawValue: String) { self.rawValue = rawValue }
}
public typealias OysterModeValue = OysterRawValue
public extension OysterModeValue {
static let adventure = OysterModeValue(rawValue: "adventure")
static let directing = OysterModeValue(rawValue: "directing")
static let acting = OysterModeValue(rawValue: "acting")
}
// start() 返回(SDK 出参,非 Codable)
public struct OysterStartTravelData: Equatable, Sendable {
public let encryptedTravelId: String // 本次体验标识
public let encryptedWorldId: String
public let mode: OysterModeValue // adventure / directing / acting
public let playUrl: String? // 当前服务端固定返回 null
public let firstFrame: String? // 首帧图地址,可能为 null(异步产生)
public let version: String // 世界版本标识(诊断用;互动 UI 请按 mode 决策)
public let aspectRatio: String? // 画幅("9:16" / "16:9");仅 acting 有值,其余模式为 nil
}
// 控制接口返回(SDK 出参,非 Codable;status 为对外枚举 OysterTravelStatus)
public struct OysterTravelStateData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus }
public struct OysterRewindTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let resumedAtSec: TimeInterval }
public struct OysterEndTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let endedAt: Date; public let duration: TimeInterval }
public struct OysterSendInstructData: Equatable, Sendable { public let encryptedTravelId: String; public let content: String; public let accepted: Bool }
// 世界探索模式操控指令(强类型枚举;取值与内部 WorldControlParams 对齐)
public struct OysterAdventureCommand: Equatable, Sendable {
public enum Translation: String { case none, front, back, left, right, frontLeft, frontRight, backLeft, backRight }
public enum Rotation: String { case none, mouseUp, mouseDown, mouseLeft, mouseRight, mouseUpLeft, mouseUpRight, mouseDownLeft, mouseDownRight }
public enum Interaction: String { case none, jump, attack, crouch, sprint }
public let translation: Translation // 本条指令的移动方向;持续移动时按帧提交,松开后停止提交
public let rotation: Rotation // 本条指令的视角方向;持续转动时按帧提交,松开后停止提交
public let interaction: Interaction // 本条指令的单次动作;调用一次即可
public init(translation: Translation = .none, rotation: Rotation = .none, interaction: Interaction = .none)
public init(_ params: WorldControlParams) // 由默认控件 WorldControlParams 便捷桥接(rawValue 一致)
}
// 统一错误(见"错误码"章节)
public struct OysterSDKError: Error {
public let code: Int
public let raw: Any? // 原始信息(内部 OysterError、网关 request_id 等),不承诺结构稳定
public var kind: OysterErrorKind // 数字 code 的类型化视图,便于穷尽 switch(计算属性)
public init(code: Int, raw: Any? = nil)
// 错误码常量见嵌套的 OysterSDKError.Code(如 .notInitialized = 100001)
}
// 错误码的类型化语义视图:本地码为具名 case,服务端 4xxxxx/5xxxxx 收敛为 .server(code:)
public enum OysterErrorKind: Equatable, Sendable {
case notInitialized, tokenMissing, tokenInvalid, noActiveTravel, invalidState, sendCommandInDirecting
case concurrentTravel, realtimeConnectFailed, realtimeJoinTimeout, firstFrameTimeout
case channelNotReady, callbackTimeout, localNetwork, responseDecodeFailed
case streamAutoEnd, proxyOrUnrecognized, featureGateDisabled
case server(code: Int), unknown(code: Int)
}
說明
- 操控指令 rawValue 為小寫駝峰命名(如
front/mouseLeft/jump),以上述列舉值為準。 mode對外取值為adventure(世界探索)/directing(即時導演)/acting(角色演繹),以OysterModeValue定義為準。aspectRatio為開放字串(目前為9:16/16:9,後續可能擴充)。判斷橫向或直向螢幕時請按宽:高解析比例,請勿窮舉已知值。
錯誤碼
SDK 統一以 OysterSDKError 回報錯誤,類型一律透過 code 區分,請勿依據「從哪條路徑取得的錯誤」來判斷類型——同一個 code 既可能從業務方法(async throws)拋出,也可能經 events 的 .error 透出。需要型別化匹配時請使用 error.kind(詳見「資料模型」章節 OysterErrorKind)。
錯誤碼:伺服器端4xxxxx/5xxxxx,用戶端本機1xxxxx。
伺服器錯誤碼(常見)
code | 含義 | 建議處理方式 |
|---|---|---|
| 參數非法(列舉非法等) | 檢查請求參數或 SDK 版本 |
| 體驗憑證( | 讓伺服器端重新下發憑證 |
| 體驗憑證( | 憑證一次性,重新下發 |
| World 不存在、已刪除或不屬於目前開發者(含憑證內世界已刪除) | 重新選擇有效 World |
| 世界狀態非就緒 | 等世界就緒後再開始 |
| 當前介面僅允許主 API Key | 臨時 Key 不可用於此介面 |
| 輸入內容違規(內容安全審查);適用於 | 修改輸入內容後重試 |
| 目前規格未開通 | 不可依容量已滿重試;請更換已開通的規格或聯絡開通(典型情況:帳號未開通角色演繹(acting)規格) |
| 容量配置暫不可用 | 稍後重試 |
| 資源不存在(世界 / Travel 歸屬或無產物) | 核對 ID / 狀態 |
| 請求與目前資源狀態衝突 | 檢查體驗狀態 |
| 目前規格併發已滿 | 待現有工作階段結束後重試(請勿與 |
| 目前可用容量不足 | 稍後重試 |
| 系統內部錯誤 | 稍後重試 / 回饋 |
| 推理資源分配/服務內部失敗 | 稍後重試 |
用戶端本機錯誤碼
code | 含義 | SDK 是否自動終止工作階段 | 建議處理方式 |
|---|---|---|---|
| SDK 未初始化即呼叫;亦包括 | 否(同步擲回,拒絕此次呼叫) | 先 |
| 未注入 HTTP 驗證 token | 否 |
|
| HTTP 驗證 token 無效 / 被拒 | 否 | 重新換取 token 後 |
| 目前無活躍體驗 | 否(拒絕該次呼叫) | 先 |
| 目前狀態/版本不允許此操作 | 否(拒絕該次呼叫) | 檢查體驗狀態 / |
| 模式不匹配(例如非 | 否(拒絕該次呼叫) | 按 |
| 並行建立/開始體驗 | 否(同步擲回) | 序列化呼叫,先 |
| 即時連線失敗 | 是 | 結束並重新開始 |
| 即時入會逾時 | 是 | 結束並重新開始 |
| 等待視訊首幀逾時 | 是 | 結束並重新開始 |
| 即時通道未就緒/傳送失敗 | 依場景而定(主動傳送失敗;心跳僅上報) | 待 |
| 回呼逾時(預設 30s) | 否 | 重試,必要時調大 |
| 加入會議後無推流,SDK 自動結束體驗 | 是 | 結束並重新開始 |
| 本機網路錯誤 | 否 | 可重試 |
| 回應解析失敗 | 依場景 | 升級 SDK / 回饋 |
| 未攜帶可識別錯誤碼 / 代理字串錯誤碼 | 否 | 可重試 |
| 被伺服器功能開關遠端停用(整體關閉或版本過低;原因見 | 是 | 按 |
如何判斷致命性:致命性不再以布林欄位公開(OysterSDKError 沒有 isFatal)。語義上「致命」專指 SDK 是否主動終止工作階段(斷開 RTC、釋放整次工作階段)——
- 會自動終止工作階段的錯誤(如
105001/105002/105003/105006/108001):宿主從狀態機終止狀態感知(status → failed,經events的.statusChanged透出),據此回到「開始體驗」前的介面即可,無需自行判定致命性。 - 呼叫拒絕類錯誤(
100001/103001/103002/103003/103004):在您主動呼叫時同步拋出或拒絕該次呼叫,不會終止工作階段。 - 其餘非致命錯誤(如
101001/101002/105005/106001/106003):不會終止工作階段,請依建議重試或重新注入 token 後繼續。
說明106001 可能有兩種原因:本機網路錯誤,或 apiHost 設定錯誤。若重試無法恢復,請檢查 apiHost 是否設定正確。
說明106003 若在填寫 model 後出現,請優先檢查模型名稱及版本是否正確、是否已為目前帳號開通,並確認 apiHost、token 與模型屬於同一帳號和區域——不匹配時閘道會以 AccessDenied 拒絕並歸一至此錯誤碼。