すべてのプロダクト
Search
ドキュメントセンター

Alibaba Cloud Model Studio:HappyOyster iOS SDK統合ガイド

最終更新日:Sep 23, 2026

これはiOSインテグレーター向けの最短のオンボーディングパスです。初期化から、画面への映像表示、制御指示の送信、体験の終了までをカバーします。完全なメソッドシグネチャ、フィールド、エラーコードについては、iOS SDK APIリファレンスを参照してください。

完全なメソッドシグネチャ、フィールド、エラーコードについては、iOS SDK APIリファレンス を参照してください。


0. 構築内容

「ワールドに入る → リアルタイムビデオ体験 → インタラクション → 終了」という最小限のエンドツーエンドループです。ランタイムのエントリーポイントは、グローバルな HappyOysterEngine.shared と、それが作成する1回限りのセッションハンドル OysterTravel です。

OysterStream.register()
HappyOysterEngine.shared: initialize → updateToken → createTravel
OysterTravel:             videoView / events → start → sendInstruct / sendCommand → end

1. 要件

項目

要件

最小 OS

iOS 15.0+

言語

Swift(async/await)

スレッディング

パブリックAPIは @MainActor です。メインスレッドで呼び出してください

インポート

import HappyOysterSDK(集約エントリーポイント、Core + World は既に @_exported 済み)

注記リアルタイム通信(AliRTC)はSDK内にカプセル化されており、インテグレーターがRTC APIを直接操作することはありません。

1.1 CocoaPods を使用して SDK を追加

SDKはCocoaPodsサブスペックを通じてコンパイル済みバイナリ(xcframework)として配布されており、パブリックCocoaPods Trunkに公開されています。バージョンで直接参照でき、ローカルのpodspecファイルは不要です。

# HappyOysterSDK / AliVCSDK_ARTC are both published on the public CocoaPods source.
source 'https://cdn.cocoapods.org/'

platform :ios, '15.0'
use_frameworks!

target 'YourApp' do
  # Aggregate entry point (Core + World); `import HappyOysterSDK` and you're set.
  pod 'HappyOysterSDK'
  # Optional default UI components (video view, control HUD).
  pod 'HappyOysterSDK/UI'
  # Video stream + AliRTC engine adapter (already depends on Stream; no need to declare it separately).
  pod 'HappyOysterSDK/StreamAliRTC'

  # RTC vendor binary: weak-linked by the SDK, not redistributed with it — bring your own (public CocoaPods source).
  pod 'AliVCSDK_ARTC', '7.11.0'
end

次に pod install を実行し、生成された .xcworkspace を開きます(.xcodeproj ではありません)。

注記HappyOysterSDK/StreamAliRTC を導入する場合、AliVCSDK_ARTC is required です。これが欠落している場合、SDKはサイレントにLoopbackにフォールバックします。接続は成功し running に達しますが、shows a black screen with no error となります。

2.2種類の認証情報(落とし穴を避けるためにまず理解してください)

SDKは not 自身で認証情報を取得または更新しません。すべての認証情報を注入する必要があります:

認証情報

取得元

用途

注入方法

HTTP 認証トークン

アプリが 独自のバックエンド から取得します

ゲートウェイへのSDK呼び出し用の一般的な認証(長期間有効ですが、更新が必要です)

updateToken(_:);SDKは最新のトークンのみを保持します

ワンタイムチケット

Travel クレデンシャル API を介して交換した後、サーバーによって発行されます

単一の体験に使用され、使用後すぐに無効化されます

createTravel(ticket:) の引数として渡されます

注記これら2つは not interchangeable です。updateToken は一般的な認証用であり、ticket は1回限りの参加用認証情報です。AK/署名鍵はサーバー上にのみ存在し、クライアントに公開されることはありません。


3. 統合手順

ライフサイクル:ストリームエンジンの登録 → 初期化 → トークンの注入 → セッションの作成 → ビデオのアタッチ+イベントのサブスクライブ → 開始 → インタラクション → 終了。

ステップ 1:ストリームエンジンを登録する

ビデオレンダリングの単一エントリーポイントです。アプリ起動時に1回呼び出してください。繰り返し呼び出しても安全です。

import HappyOysterSDK
import HappyOysterStream

OysterStream.register()

ステップ 2:SDK の初期化

他のAPIを呼び出す前に、一度初期化する必要があります。To switch gateway or model, just call it again — アイドル状態であれば最新の構成が有効になり、注入されたトークンは保持されます。トラベルの実行中のみ呼び出しが無視されるため、その場合は先に end() を実行してください。

let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
    apiHost: "[workspace-id].[region].maas.aliyuncs.com",  // Model Studio gateway host
    model: "happyoyster-1.0-adventure"                     // Versioned model name enabled for your account, required
))
// Optional: override the log level / signalling callback timeout
// OysterConfig(apiHost: "…", model: "…", logLevel: .debug, callbackTimeoutMs: 30_000)

// Both apiHost and model are required with no default: Happy Oyster is split into
// per-mode sub-models. See the Happy Oyster model documentation for available names
// and versions; they must match the account and region of your token.

ステップ 3:HTTP認証トークンを注入する(プッシュ)

独自のバックエンドから一時的なModel Studio API Keyを取得して注入してください。有効期限が切れた後は再注入が必要です。

let token = await fetchTokenFromYourBackend()
engine.updateToken(token)

ステップ 4:セッションハンドルの作成

使い捨ての ticket から OysterTravel を作成します。Nothing is connected yet が、ビデオビューはすでに利用可能です。

let travel = try engine.createTravel(ticket: ticket)

ステップ 5:ビデオビューのアタッチとイベントのサブスクライブ

SDKがビューを提供し、ホスト側がそれを配置します。初期のステータス変更を見逃さないよう、before 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 — see the API Reference
        }
    }
}

ステップ 6:体験の開始

接続して再生を開始します。その後、SDKが automatically リアルタイム接続とステータスポーリングを維持し、events を通じてステータスを通知します。

let data = try await travel.start()
// data.encryptedTravelId —— identifier of this experience, used for diagnostics / server reconciliation
// data.encryptedWorldId  —— identifier of the world you entered
// data.mode              —— adventure / directing / acting, determines the interaction UI
// data.aspectRatio       —— "9:16" / "16:9"; set for acting only, use it to pick the player orientation

注記start(maxExperienceTimeSec:) は adventure にのみ適用されます。directing および acting はこのパラメータを無視します。

このトラベルにおけるワールドのモードは、ステップ 2 で initialize に渡した model と一致している必要があります。モードごとのモデル(happyoyster-1.0-adventure / -directing / -acting)を使用する場合、各モデルは独自のゲートウェイアプリケーションルートとなるため、1回の initialize で対応できるのは単一モードのワールドのみです。ticket はそのワールドのモードのルート配下でサーバーによって発行されたものであり、start() はそれを現在設定されている model のルートに送信します。不一致はこのステップで失敗します。

異なるモードのワールドに入る前に、一致するモデルを指定して再度 initialize() を呼び出してください。cleanup() や再 updateToken() は不要です:

HappyOysterEngine.shared.initialize(config: OysterConfig(
    apiHost: "…", model: "happyoyster-1.0-acting"))   // the model for this world's mode

アイドル状態(トラベルが実行中でない場合)は最新の構成が有効になり、注入されたトークンは保持されます。SDKは事前にモデルとワールドの一致を検証できません。mode はこのステップのレスポンス(data.mode)で初めて届きます。呼び出し前、SDKが保持するのは不透明な ticket とモデル名のみです。単一モードのみを提供するアプリは影響を受けず、初期化は1回で済みます。

注記トラベルの実行中に initialize() を繰り返し呼び出すと、警告と共に無視されます。先に end() を実行してください。

ステップ 7:リアルタイムインタラクション(モードに応じて選択)

if data.mode == .adventure {
    // Adventure: direction/view/action control (fire-and-forget; never throws, failures come via events)
    travel.sendCommand(OysterAdventureCommand(translation: .front, interaction: .jump))
} else {
    // Directing and acting: text instructions
    _ = try await travel.sendInstruct(content: "Pan to the castle; the hero starts running")
}

sendCommand は内部で 42ms(24FPS)の最新優先サイクルでスロットリングされるため、ホスト側は高頻度で呼び出すことができます:

  • ジャンプ、攻撃、しゃがみ、ダッシュなどのワンショットアクションは1回だけ送信されます。
  • 移動と視点回転は、入力中は継続的に送信され、離した時点で呼び出しを停止するだけです。
  • リリース時に none を送信したり flushCommands() を呼び出したりする必要はありません。SDKは自身で停止コマンドを生成することはなく、リアルタイムチャネルが静かになるとサーバー側でアクションが終了します。

ステップ 8:一時停止/再開/巻き戻し(モード依存)

_ = try await travel.pause()           // supported by directing and acting
_ = try await travel.resume()
_ = try await travel.rewind(toSec: 10) // directing only

注記adventureはこれらの機能をnoneもサポートしていません。actingは一時停止と再開をサポートしますが、巻き戻しはサポートしません。そのため、これらのモードでは巻き戻しのエントリーポイントを非表示にしてください。不一致の呼び出しは、SDKによってローカルで拒否されます(103003 / 103002)。

ステップ 9:体験の終了

接続を解除し、ポーリングを停止して、すべてのセッションリソースを解放します。ticket は消費されます。冪等性があります — every exit path must funnel into it。

_ = try? await travel.end()
eventTask.cancel()

注記SDKの破棄時やゲートウェイの切り替え時には await engine.cleanup() を呼び出してください。


4. 完全な例(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 provides the view, host places it
                    .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()                        // issued by your server
            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: "Suddenly it starts to pour")
            }
        } catch let error as OysterSDKError {
            // Handle start failure (see the error code table in the API Reference)
        } catch {}
    }

    @MainActor private func end() async {
        _ = try? await travel?.end()                                // Step 9
        eventTask?.cancel()
        travel = nil
    }
}

5. ベストプラクティス

  • Lifecycle:アプリ起動時にできるだけ早く OysterStream.register() + initialize() をそれぞれグローバルに1回ずつ呼び出してください。リアルタイム接続とリソースの解放を確実にするため、体験ページを離れる際は常に end() を呼び出してください。OysterTravel は single-use です。終端状態に達した後は、createTravel を通じて新しいものを作成してください。
  • Token renewal:体験を開始する前にHTTPトークンが最新であることを確認してください。認証関連のエラーコールバックを受け取った場合は、新しいトークンを取得して updateToken を呼び出してください(致命的ではなく、体験は終了しません)。
  • Error severity:致命的なエラーの場合、SDKは現在の体験を自動的に終了し、events の .error を通じてエラーを通知し、failed 終端状態に移行します。「体験開始」前の画面に戻る必要があります。致命的でないエラーは通知されるのみで、再試行可能です。完全なエラーコード表については APIリファレンス を参照してください。
  • Mode adaptation:ディレクティングおよびアクティングモードではテキスト入力(sendInstruct)が表示されます。アドベンチャーモードではコントロールウィジェット(sendCommand)が表示されます。巻き戻しのエントリーポイントが表示されるのはディレクティングのみです。アクティングワールドの場合、aspectRatio からプレーヤーの向きを選択してください(デフォルトは縦画面 9:16)。

6. 次のステップ