全部產品
Search
文件中心

Alibaba Cloud Model Studio:HappyOyster iOS SDK 接入指南

更新時間:Sep 23, 2026

本文是 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(async/await)

執行緒

公開介面 @MainActor,在主執行緒呼叫

引入

import HappyOysterSDK(聚合入口,已 @_exported Core + World)

說明即時通訊(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 呼叫閘道的通用驗證(長期、需續期)

updateToken(_:),SDK 只存最新一個

一次性 ticket

您的伺服器經 Travel 憑證介面換取後下發

僅用於一次體驗,用完即失效

作為 createTravel(ticket:) 輸入參數

說明兩者不可混用: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