本文是 iOS 整合方的最短上手路徑:從初始化,到畫面出來,到傳送控制指令,再到結束體驗。完整的方法簽章、欄位、錯誤碼以 iOS SDK API Reference 為準。
完整的方法簽章、欄位、錯誤碼以 HappyOyster iOS SDK API Reference 為準。
您將完成什麼
一次「進入世界 → 即時視訊體驗 → 互動 → 結束」的最小閉環。執行時入口是全域的 HappyOysterEngine.shared,以及它建立出的單次工作階段控制代碼 OysterTravel。
OysterStream.register()
HappyOysterEngine.shared: initialize → updateToken → createTravel
OysterTravel: videoView / events → start → sendInstruct / sendCommand → end
環境需求
項目 | 要求 |
|---|---|
最低系統版本 | iOS 15.0+ |
語言 | Swift( |
執行緒 | 公開介面 |
引入 |
|
說明即時通訊(AliRTC)由 SDK 內部封裝,整合方無需直接接觸 RTC API。
透過 CocoaPods 引入 SDK
SDK 以預編譯二進位檔(xcframework)按 subspec 分發,已發佈到 CocoaPods 公開 Trunk——按版本號直接引入即可,無需本機 podspec 檔案。
# HappyOysterSDK / AliVCSDK_ARTC 均发布在 CocoaPods 公开源。
source 'https://cdn.cocoapods.org/'
platform :ios, '15.0'
use_frameworks!
target 'YourApp' do
# 聚合入口(Core + World),import HappyOysterSDK 一行即用。
pod 'HappyOysterSDK'
# 可选:默认 UI 组件(视频视图、操控 HUD)。
pod 'HappyOysterSDK/UI'
# 视频流 + AliRTC 引擎适配器(已依赖 Stream,无需单独声明)。
pod 'HappyOysterSDK/StreamAliRTC'
# RTC 厂商二进制:SDK 弱引用、不随 SDK 分发,由集成方自行引入(CocoaPods 公开源)。
pod 'AliVCSDK_ARTC', '7.11.0'
end
然後執行 pod install,並開啟產生的 .xcworkspace(而非 .xcodeproj)。
說明引入 HappyOysterSDK/StreamAliRTC 時 AliVCSDK_ARTC 為必要項目:若缺失,SDK 會靜默回落 Loopback——能連上且狀態走到 running,但會黑屏且不報錯。
驗證模型(兩類憑證)
SDK 自己不取得、不重新整理任何憑證,全部由您注入:
憑證 | 來源 | 用途 | 注入方式 |
|---|---|---|---|
HTTP 驗證 token | 您的 App 從自家後端取得 | SDK 呼叫閘道的通用驗證(長期、需續期) |
|
一次性 ticket | 您的伺服器經 Travel 憑證介面換取後下發 | 僅用於一次體驗,用完即失效 | 作為 |
說明兩者不可混用:updateToken 是通用驗證,ticket 是一次性入房憑證。AK / 簽名金鑰只存在於您的伺服器,用戶端永不接觸。
接入步驟
生命週期:註冊串流引擎 → 初始化 → 注入 token → 建立工作階段 → 掛載視訊 + 訂閱事件 → start 播放 → 互動 → end。
Step 1:註冊串流引擎
輸出畫面的唯一入口。建議 App 啟動時呼叫一次,重複呼叫安全。
import HappyOysterSDK
import HappyOysterStream
OysterStream.register()
Step 2:初始化 SDK
呼叫任何其他 API 前必須初始化一次。換閘道或換模型時直接再呼叫一次即可(空閒時以最新 config 為準,已注入的 token 保留);僅當有進行中的體驗時本呼叫被忽略,需先 end()。
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
apiHost: "[workspace-id].[region].maas.aliyuncs.com", // 百炼网关地址
model: "happyoyster-1.0-adventure" // 已开通的模型名(含版本),必填
))
// 可选:覆盖日志级别 / 信令回调超时
// OysterConfig(apiHost: "…", model: "…", logLevel: .debug, callbackTimeoutMs: 30_000)
// apiHost 与 model 都必填、都没有默认值:HappyOyster 按模式拆成了不同子模型,
// 模型名(含版本)取值见 HappyOyster 系列模型文档,须与 token 同账号、同区域。
Step 3:注入 HTTP 驗證 token(push)
從您自己的後端取得百煉臨時 API Key 後注入。過期後重新取得並再次注入即可。
let token = await fetchTokenFromYourBackend()
engine.updateToken(token)
Step 4:建立工作階段控制代碼
用一次性 ticket 建立 OysterTravel。此時尚未建立連線,視訊檢視畫面已可取用。
let travel = try engine.createTravel(ticket: ticket)
Step 5:掛載視訊 + 訂閱事件
SDK 輸出檢視畫面、宿主擺放;start() 前就訂閱事件,避免漏掉早期狀態。
containerView.addSubview(travel.videoView) // SwiftUI 用 OysterVideoView(travel:)
let eventTask = Task {
for await event in travel.events {
switch event {
case .statusChanged(let status): render(status) // running / paused / ended / failed…
case .error(let error): handle(error) // error.code / error.kind 见 API Reference
}
}
}
Step 6:開始體驗
建立連線並開始播放。成功後 SDK 內部自動維持即時連線與狀態輪詢,狀態經 events 透出。
let data = try await travel.start()
// data.encryptedTravelId —— 本次体验的标识,问题排查 / 服务端对账时使用
// data.encryptedWorldId —— 本次进入的世界标识
// data.mode —— adventure(世界探索)/ directing(实时导演)/ acting(角色演绎),决定交互 UI
// data.aspectRatio —— 画幅("9:16" / "16:9");仅 acting 有值,用于决定播放器方向
說明start(maxExperienceTimeSec:) 只對 adventure 生效,directing 與 acting 忽略該參數。
本次 travel 的世界 mode 必須與 Step 2 initialize 傳入的 model 匹配。模型按 mode 拆分後(happyoyster-1.0-adventure / -directing / -acting),每個模型是一條獨立的閘道應用程式路由,一次 initialize 只服務一種 mode 的世界;ticket 由您的伺服器在「該世界 mode 對應的模型」路由下簽發,start() 會把它發往當前 model 那條路由,兩者不一致時本步失敗。
進入另一種 mode 的世界之前,用對應模型再 initialize() 一次——不需要 cleanup(),也不需要重新 updateToken:
HappyOysterEngine.shared.initialize(config: OysterConfig(
apiHost: "…", model: "happyoyster-1.0-acting")) // 换成本次世界 mode 对应的模型
空閒(沒有進行中的體驗)時以最新 config 為準,已注入的 token 保留。SDK 不會替您提前校驗模型與世界是否匹配:mode 要等本步的回應(data.mode)才知道,呼叫前 SDK 只有不透明的 ticket 和模型名稱。只提供一種 mode 的 App 不受影響,初始化一次即可。
說明若有進行中的體驗,重複呼叫 initialize() 會被忽略並發出警告,需先 end() 再切換。
Step 7:即時互動(按模式選擇)
使用 data.mode 決定互動方式:
if data.mode == .adventure {
// 世界探索:方向/视角/动作控制(fire-and-forget,不抛错,失败经 events 透出)
travel.sendCommand(OysterAdventureCommand(translation: .front, interaction: .jump))
} else {
// 实时导演(directing)与角色演绎(acting):文本指令驱动
_ = try await travel.sendInstruct(content: "镜头转向城堡,主角开始奔跑")
}
sendCommand 內部按 42ms(24FPS)週期做 latest-wins 節流,宿主可以高頻呼叫:
- 跳躍、攻擊、蹲下、衝刺等單次動作只呼叫一次。
- 移動和視角在按住期間持續呼叫,放開時直接停止呼叫。
- 不需要在放開時傳送
none或呼叫flushCommands();SDK 也不會主動產生停止指令,伺服器會在即時通道不再收到訊息後自行結束動作。
Step 8:暫停 / 恢復 / 回溯(按模式)
_ = try await travel.pause() // directing / acting 支持
_ = try await travel.resume()
_ = try await travel.rewind(toSec: 10) // 仅 directing 支持
說明adventure 不支援這三個介面;acting 支援暫停 / 恢復但不支援回溯——請在這兩種模式下隱藏回溯入口,不匹配的呼叫會被 SDK 本機拒絕(103003 / 103002)。
Step 9:結束體驗
斷開即時連線、停止輪詢、釋放全部工作階段資源;本次 ticket 同時失效。冪等,任意退出路徑都要收口到它。
_ = try? await travel.end()
eventTask.cancel()
說明退出 SDK 或切換閘道時再呼叫 await engine.cleanup()。
完整範例(SwiftUI)
import SwiftUI
import HappyOysterSDK
import HappyOysterStream
@main
struct MyApp: App {
init() {
OysterStream.register() // Step 1
HappyOysterEngine.shared.initialize(config: OysterConfig( // Step 2
apiHost: "[workspace-id].[region].maas.aliyuncs.com",
model: "happyoyster-1.0-adventure"
))
}
var body: some Scene { WindowGroup { TravelScreen() } }
}
struct TravelScreen: View {
@State private var travel: OysterTravel?
@State private var eventTask: Task<Void, Never>?
var body: some View {
ZStack {
if let travel {
OysterVideoView(travel: travel) // Step 5:SDK 出视图、宿主摆放
.ignoresSafeArea()
} else {
Color.black.ignoresSafeArea()
}
}
.task { await start() }
.onDisappear { Task { await end() } }
}
@MainActor private func start() async {
let engine = HappyOysterEngine.shared
engine.updateToken(await fetchToken()) // Step 3
do {
let ticket = await fetchTicket() // 由你的服务端下发
let travel = try engine.createTravel(ticket: ticket) // Step 4
self.travel = travel
eventTask = Task { // Step 5
for await event in travel.events {
switch event {
case .statusChanged(let status): print("status: \(status)")
case .error(let error): print("error: \(error.code)")
}
}
}
let data = try await travel.start() // Step 6
if data.mode == .adventure { // Step 7
travel.sendCommand(OysterAdventureCommand(translation: .front))
} else {
_ = try await travel.sendInstruct(content: "突然下起了大雨")
}
} catch let error as OysterSDKError {
// 处理开始失败(error.code 见 API Reference 错误码表)
} catch {}
}
@MainActor private func end() async {
_ = try? await travel?.end() // Step 9
eventTask?.cancel()
travel = nil
}
}
最佳實務
- 生命週期:App 啟動盡早
OysterStream.register()+initialize(),全域各一次;離開體驗頁時務必end(),確保即時連線與資源釋放;OysterTravel是單次使用的,到終態後需重新createTravel。 - token 續期:進體驗前確保 HTTP token 新鮮;收到驗證類錯誤(
101001/101002)後重新取得並updateToken(非致命,不終止體驗)。 - 錯誤分級:致命錯誤 SDK 會自動終止本次體驗,經
events的.error透出、並以狀態機終態(failed)收尾,您應回到「開始體驗」前的介面;非致命錯誤僅透出,可重試。錯誤碼總表見 API Reference。 - 模式適配:即時導演與角色演繹模式展示文字輸入(
sendInstruct),世界探索模式展示操控控制項(sendCommand);回溯入口只在即時導演模式展示。角色演繹世界按aspectRatio決定播放器方向(預設直式9:16)。
下一步
- 完整 API(
pause()/resume()/rewind(toSec:)、資料模型、錯誤碼)→ iOS SDK API Reference