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

Alibaba Cloud Model Studio:HappyOyster iOS SDK APIリファレンス

最終更新日:Sep 25, 2026

HappyOyster iOS SDKのエントリポイントは、プロセスレベルのシングルトンであるHappyOysterEngine.sharedです。ビジネスメソッドはすべてasync throwsであり、失敗時にはOysterSDKErrorをスローします。単一の体験はOysterTravelハンドルによって管理され、UIKitとSwiftUIの両方で、アドベンチャー、ディレクション、アクティングの3つのモードにまたがります。

注記

  • 本文書は、Happy Oyster iOS SDKの外部機能説明 + インターフェースリファレンスです。各パブリック型とメソッドのパラメータ、タイミング、使用方法、および短い例を1つずつ説明しています。
  • 完全な統合フロー(プロジェクトのセットアップ、依存関係の設定、サーバー側の連携、エンドツーエンドの実行)については、サンプルプロジェクトのドキュメントを参照してください。ここでは繰り返しません。

1. コアコンセプト

まず、以降の内容を読みやすくするためにいくつかの用語を説明します。

概念

説明

token

HTTP 認証トークン(Bailian 一時 API キー)。サーバーが Bailian API を介して交換し、配信します。updateToken(_:) を通じて注入され、SDK はゲートウェイを要求する際に HTTP Bearer としてこれを保持します。SDK は最新のもののみを保持し、永続化や更新は行いません。有効期限が切れた後、再交換して再注入します。

ticket

ワンタイムトライアルクレデンシャル。サーバーがオープンプラットフォームの get-travel-credential を介して交換します(プレフィックス tk_、有効期間 30 分、使い捨て)。createTravel(ticket:) の引数として使用され、体験が終了するか有効期限が切れると無効になり、再利用できません。

World

キャラクターやシーンを含むAIワールド。サーバーによって作成および管理され、SDKは関与しません。

Travel

1つのOysterTravelハンドルに対応する単一のリアルタイム体験。使い捨てであり、終端状態に達すると無効化され、エンジン経由で新しいcreateTravelが必要になります。

セッションステータス

OysterTravelStatus:idle → prepare → running → pausing → paused(一時停止後は再接続により prepare → running に戻ります)、および2つの終端状態 ended / failed(§7 を参照)。

モード

adventure(方向/視点/アクションコマンド)、directing(テキスト駆動のストーリーライン)、または acting(テキスト駆動のキャラクターパフォーマンス)。start() によって返される mode に応じて UI が決定されます。各モードがサポートする内容については §2 を参照してください。

2. 概要

この SDK を統合すると、アプリは AI によってリアルタイムに生成される「ワールド」に入り、3つのモード(adventure、directing、または acting)でリアルタイムインタラクティブビデオ体験ができます。体験の開始 → リアルタイム再生 → リアルタイムインタラクション → プロセス制御(一時停止/再開/巻き戻し/終了) → ステータスおよびエラーコールバック。

各モードは異なる機能セットをサポートしています。start()によって返されるmodeに基づいてUIを制御してください:

機能

adventure

directing

acting

sendCommand(方向/ビュー/アクション)

はい

いいえ

いいえ

sendInstruct(テキスト指示)

いいえ

はい

はい

pause() / resume()

いいえ

はい

はい

rewind(toSec:)

いいえ

はい

いいえ — エントリポイントを非表示にする

end()

はい

はい

はい

start(maxExperienceTimeSec:)

適用される

無視される

無視される

注記actingワールドはポートレート優先です:start()はセッション用にaspectRatio(9:16 / 16:9)を返します。ストリームをプルする前に、これを使用してプレーヤーの向きとコンテナサイズを選択してください(§8を参照)。

import HappyOysterSDK。コアとなるエントリポイントは2つの型です。

HappyOysterEngine — オーケストレーションのエントリポイントであり、プロセスレベルのシングルトンHappyOysterEngine.shared。

メソッド / プロパティ

説明

initialize(config:)

ランタイムを初期化し、リアルタイムエンジンを自動的に登録します(createTravelの前に1回だけ)

updateToken(_:)

HTTP認証トークンの注入 / 更新

createTravel(ticket:)

ワンタイム認証情報でセッションハンドルを作成

cleanup()

リソースを解放(initializeを再度実行可能)

version

現在のSDKバージョン

OysterTravel — createTravel(ticket:)によって作成される、1回の体験用のセッションハンドル。

メソッド / プロパティ

説明

videoView / OysterVideoView(travel:)

再生ビュー(UIKit / SwiftUI)

events / status

状態変更 + エラーのプッシュストリーム / 観測可能な現在の状態

start() / start(maxExperienceTimeSec:)

接続して再生(オプションでアドベンチャー体験の最大時間をリクエスト)

sendInstruct(content:)

ディレクティングモードのテキスト指示

sendCommand(_:) / flushCommands()

アドベンチャーモードの制御

pause() / resume()

一時停止 / 再開(directingおよびacting)

rewind(toSec:)

巻き戻し(一時停止時のみ)

end()

終了(冪等性あり、必ず呼び出してください)

pauseLocalAudioCapture() / resumeLocalAudioCapture()

マイクの一時的な譲渡 / 復元

いくつかのヘルパーエクスポートもあります:SDK の内部ログを引き継ぐための OysterLog、バージョンを読み取るための HappyOysterEngine.version、OysterVideoView は SwiftUI 再生ビューです(videoView と同等)。使用方法は後述します。

ライフサイクル:初期化 → トークンの注入 → セッションの作成 → ビデオのマウント + イベントの購読 → 再生の開始 → インタラクション → 終了。SDK はワールドの作成や管理(サーバー側で行われます)を担当せず、低レベルのリアルタイム通信の詳細を公開することもありません。

3. Quick Start

初期化から終了までの完全なフローを、ステップごとのコメント付きで示します。各APIの詳細は§6にあります。

import HappyOysterSDK

// 1) Initialize (as early as possible after app launch, once before createTravel)
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
    apiHost: "[workspace-id].[region].maas.aliyuncs.com",// For the API host, refer to the Bailian documentation https://www.alibabacloud.com/help/en/model-studio/base-url
    model: "happyoyster-1.0-adventure"                  // Versioned model name enabled for your account, required; see the Happy Oyster model documentation
))

// 2) (Optional) Take over logging: set level + custom output
OysterLog.setMinimumLevel(.info)
OysterLog.setHandler { level, tag, message in
    print("[Oyster][\(level)][\(tag)] \(message)")
}

// 3) Inject the HTTP auth token (Bailian temporary API Key, delivered by your server)
engine.updateToken(temporaryApiKey)

// 4) Create a session handle with a one-time ticket (not yet connected)
let travel = try engine.createTravel(ticket: ticket)

Task { @MainActor in
    // 5) Mount the video (UIKit; for SwiftUI use OysterVideoView(travel:))
    containerView.addSubview(travel.videoView)

    // 6) Subscribe to events before start, to avoid missing early statuses
    let eventTask = Task {
        for await event in travel.events {
            switch event {
            case .statusChanged(let status): render(status)   // §7 status table
            case .error(let error):          handle(error)     // §9 error.code / error.kind
            }
        }
    }

    do {
        // 7) Connect and play
        let data = try await travel.start()

        // 8) Interact based on mode
        if data.mode == .directing {
            _ = try await travel.sendInstruct(content: "Suddenly it starts to pour")
        } else {
            travel.sendCommand(OysterAdventureCommand(translation: .front))
        }
    } catch let error as OysterSDKError {
        handle(error)
    }

    // 9) Funnel every exit path into a single end()
    _ = try? await travel.end()
    eventTask.cancel()
}

// Exit the SDK / switch gateway: await engine.cleanup()

注記プロジェクトのセットアップ、依存関係の設定(必要なAliRTCアダプタとベンダーバイナリを含む)、サーバー側の連携、およびエンドツーエンドの実行については、サンプルプロジェクトのドキュメントを参照してください。

4. 要件

項目

Requirement

最小OS

iOS 15.0+(すべてのパブリック型には@available(iOS 15.0, *)が付けられています)

Language

Swift (async/await)

並行処理

メインスレッドアクセス(エントリ型には@MainActorが付けられています)

Network

パブリックネットワークアクセスが必要

権限

Info.plistにはNSMicrophoneUsageDescriptionを含める必要があります(下記参照)

マイクの権限:リアルタイムインタラクティブビデオ体験には、双方向(アップリンク + ダウンリンク)のリアルタイムオーディオ/ビデオチャネル が必要なため、SDK は実行中にローカルマイクを占有します。これは録音ではありません。Info.plist には NSMicrophoneUsageDescription を指定する必要があります。そうしないと、リアルタイムキャプチャの開始時にクラッシュします。マイクへの排他アクセスが必要な場合(例:音声認識)、pauseLocalAudioCapture() / resumeLocalAudioCapture() を使用して一時的に譲渡および復元します(§6.2 を参照)。

5. 統合と認証

5.1 統合と依存関係

コードレベルでは import HappyOysterSDK だけで十分です。SDK はコンパイル済みのバイナリ(xcframework)として CocoaPods の subspecs を介して配布され、パブリックな CocoaPods Trunk に公開されています。Podfile に依存関係を宣言してください:

# HappyOysterSDK / AliVCSDK_ARTC are both published on the public CocoaPods source.
pod 'HappyOysterSDK', '1.0.3'              # Aggregate entry point (Core + World)
pod 'HappyOysterSDK/UI', '1.0.3'           # Optional default UI components (video view, control HUD)
pod 'HappyOysterSDK/StreamAliRTC', '1.0.3' # Video stream + AliRTC engine adapter (already depends on Stream)

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

注記HappyOysterSDK/StreamAliRTCをプルインする際は、常に**AliVCSDK_ARTCが必要です**。これが欠落している場合、SDKはサイレントにLoopbackにフォールバックします。接続してrunningに到達することはできますが、エラーなしで黒い画面が表示されます。

プロジェクトのセットアップとエンドツーエンドの初期化フローはサンプルプロジェクトで扱われています。サンプルプロジェクトのドキュメントを参照してください。本文書はインターフェース自体に焦点を当てています。

5.2 認証モデル

SDKはトークンの取得や更新を行わず、軽量に保たれています。認証には2つのレイヤーがあり、インテグレーターがそのライフサイクルを管理します:

  1. HTTP 認証トークン(Bailian 一時 API キー):サーバーが Bailian API を介して交換し、配信します。updateToken(_:) を通じて注入されます。一部の内部 SDK サービスはBailian ゲートウェイを直接呼び出し、認証のためにこのトークンを保持します。したがって、これは独自のビジネスサービスのトークンではなく、Bailian が発行した一時 API キーである必要があります。SDK は最新のもののみを保持し、永続化や更新は行いません。有効期限が切れた後、再交換して再注入します。
  2. ワンタイムトライアルクレデンシャル ticket:サーバーがオープンプラットフォームの get-travel-credential を介して交換します(プレフィックス tk_、有効期間 30 分、使い捨て)。createTravel(ticket:) の引数として使用され、体験が(正常または異常に)終了するか、有効期限が切れると無効になり、再利用できません。

注記AK、署名キー、その他の高権限クレデンシャルはサーバー上にのみ存在し、クライアントSDKがそれらに触れることはありません。クライアントが受け取るのは常に短命な一時APIキーです。

注記機能ゲート(リモートスイッチ/強制アップグレード):サーバーは SDK をリモートで無効化したり、サポートされる最低バージョンを設定したりできます。無効化されている間、ゲートされた呼び出し(start / pause / resume / rewind / sendInstruct / sendCommand)は 108001 で拒否され、実行中の体験は SDK によって終了されます(§9 を参照)。OysterSDKError.raw には人間が読める理由が含まれます。バージョンが低すぎる場合は、ユーザーにアップグレードを促します。

トークンの有効期限切れへの対応:上記の HTTP 認証トークンの有効期限が切れた後、サーバーに新しいトークンを即座に要求し、updateToken(_:) を介して注入します。トークンの有効期限が切れているかどうかを判定する必要がある箇所は2つあります:

  1. engine.createTravelやtravel.startなどのAPIを呼び出す際は、エラーを処理し、トークンの期限切れ/無効のエラータイプ(101001 / 101002)を確認してください。新しいトークンを注入した後、対応するAPIを再度呼び出してください。
  2. OysterTravelEventの.errorイベントをリッスンする際、トークンの期限切れ/無効のエラータイプを確認し、トークンを再要求して注入してください。

6. APIリファレンス

エントリ型は2つあり、どちらも@MainActorおよび@available(iOS 15.0, *)です。ビジネスメソッドはasync throwsであり、失敗時にはOysterSDKErrorをスローします。戻り値を持つメソッドはすべて@discardableResultとマークされています。

6.1 HappyOysterEngine

プロセスレベルのシングルトンであり、オーケストレーションのエントリポイントです。initは非パブリックです。常にHappyOysterEngine.sharedを使用してください。自分でインスタンス化しないでください(基盤となるリアルタイムエンジンもプロセスシングルトンです)。

@MainActor @available(iOS 15.0, *)
public final class HappyOysterEngine {
    public static let shared: HappyOysterEngine            // the unique process-level instance
    public static let version: String                      // current SDK version

    @discardableResult
    public func initialize(config: OysterConfig) -> Bool  // initialize (once before createTravel)
    public func updateToken(_ token: String)               // inject/update the HTTP auth token
    public func createTravel(ticket: String) throws -> OysterTravel  // create a session handle with a one-time credential
    public func cleanup() async                            // release resources (can initialize again)
}

initialize(config:)

  • 目的:ランタイムを初期化し、リアルタイムエンジンを自動的に登録します(ホスト側での手動登録は不要)。
  • パラメータ:config.apiHostはBailianゲートウェイURLであり、ビジネスサーバーではありません。これは必須です。プレリリース/トライアル環境では、対応するゲートウェイを明示的に渡す必要があり、そうしないとリクエストは失敗します(例:106001、ドメインが解決できない)。config.modelはアカウントで有効化されたバージョン付きモデル名であり、同様にデフォルト値なしで必須です。Happy Oysterはモードごとのサブモデルに分割されているため、SDKはどれを使用するか推測できません。両方とも、注入されたトークンと同じアカウントおよびリージョンに属している必要があります。その他のフィールドは§8 OysterConfigを参照してください。
  • **1つのモデルは1つのmode**に対応します。モードごとのモデルはそれぞれ独自のゲートウェイルートを持つため、**1回のinitializeはその1つのmode**のワールドにのみ対応します。アプリが複数のモードのワールドを提供する場合、異なるモードのワールドに入る前に、一致するモデルを指定してinitialize()を再度呼び出すだけです。アイドル状態では最新の構成が優先され、cleanup()は不要で、注入されたトークンは保持されます。Travelの実行中は呼び出しが無視されるため、事前にend()を実行してください。ワールドのmodeと一致しないモデルは、ゲートウェイによってAccessDeniedとして拒否され、106003に正規化されます。
  • 使用タイミング:アプリ起動後できるだけ早い段階で、createTravelの前に1回呼び出します。
  • 戻り値:Bool — 渡した config が有効になったかどうか。以下の2つのケースでは false になります:設定が無効である(apiHost / model が空白、または有効なゲートウェイ URL を形成していない)、またはトラベルが実行中であるため呼び出しが無視された場合。どちらのケースでも、ランタイムは変更されません。
  • 注意:アイドル状態では、再度呼び出すと新しい構成でランタイムが再構成されます(apiHost / modelの切り替えにcleanup()は不要で、注入されたトークンは保持されます)。Travelの実行中は警告のみを出力するno-opとなるため、事前にend()を実行してください。無効な構成ではランタイムは変更されません。再initializeが成功したかどうかを判断するためにisReadyを使用しないでください。拒否された場合、以前の構成が引き続き有効であり、isReadyはtrueのままです。isReadyは「現在エンジンが使用可能か」に答え、戻り値は「渡した構成が反映されたか」に答えます。

OysterLog

  • 目的:SDKの内部ログを設定および引き継ぎ、独自のログモジュールに出力します。レベルを設定するためのsetMinimumLevel(_:)と、カスタム出力用のsetHandler(_:)を提供します(§3の例を参照)。

updateToken(_:)

  • 目的:HTTP認証トークン(Bailian一時APIキー、§5.2)を注入/更新します。
  • 使用タイミング:initializeの後、いつでも呼び出し可能です。トークンの有効期限が切れた後、または認証関連のエラー(101001 / 101002)を受け取った後に、トークンを再交換して再度呼び出してください。
  • 注釈:initializeされていない場合、警告と共に何もしません。

createTravel(ticket:)

  • 目的:ワンタイムのticketを使用して単一のセッションハンドルを作成します。
  • パラメータ:ticketはワンタイムクレデンシャルです。作成されると、この体験のために占有されたとみなされます。
  • 使用タイミング:新しい体験の前に呼び出します。返されるハンドルはまだ接続されておらず、その後travel.start()を呼び出す必要があります。ビデオは返されたハンドルから取得されます(§6.2を参照)。
  • 注(同期throws):initializeされていない場合は100001をスローします。前のTravelがend()される前に再度呼び出された場合は103004をスローします(各エンジンは一度に1つのアクティブなTravelのみを許可します)。

cleanup()

  • 目的:SDKリソースを解放します(アクティブなtravel、ランタイム設定、トークンを終了)。
  • 使用タイミング:SDKを完全に終了するとき、またはconfigを変更する必要があるとき。
  • 注:async — 内部的には、まず現在のアクティブなtravelを確実にend()し、その後ランタイムを破棄するため、fire-and-forgetにはなりません。解放後は再度initializeできます。

6.2 OysterTravel

createTravel によって作成されるセッションハンドル。使い捨てであり、終端状態(end / サーバー側の終了 / 失敗)に達すると無効化され、エンジン経由で新しい createTravel が必要になります。これは ObservableObject(@Published status、SwiftUI を直接駆動可能)でもあります。

@MainActor @available(iOS 15.0, *)
public final class OysterTravel: ObservableObject {
    @Published public private(set) var status: OysterTravelStatus   // current external status (observable)
    public var isEnded: Bool { get }                                // whether a terminal state is reached (synchronously readable)
    public var videoView: UIView { get }                            // UIKit playback view; for SwiftUI use OysterVideoView(travel:)
    public var events: AsyncStream<OysterTravelEvent> { get }       // status change + error, multi-subscribe

    @discardableResult public func start() async throws -> OysterStartTravelData   // connect and play
    @discardableResult public func start(maxExperienceTimeSec: Int?) async throws -> OysterStartTravelData  // connect and play + request max experience duration (adventure mode only)
    @discardableResult public func pause() async throws -> OysterTravelStateData   // pause (directing / acting)
    @discardableResult public func resume() async throws -> OysterTravelStateData  // resume
    @discardableResult public func rewind(toSec: TimeInterval) async throws -> OysterRewindTravelData  // rewind (paused only)
    @discardableResult public func end() async throws -> OysterEndTravelData       // end (idempotent, be sure to call)
    @discardableResult public func sendInstruct(content: String) async throws -> OysterSendInstructData  // directing-mode text instruction

    public func sendCommand(_ command: OysterAdventureCommand)      // adventure-mode control (fire-and-forget)
    public func flushCommands()                                     // manually send an already-submitted pending command
    public func pauseLocalAudioCapture() async                      // temporarily yield the microphone
    public func resumeLocalAudioCapture() async                     // restore microphone occupation
}

ビデオビュー videoView / OysterVideoView(travel:)

  • 目的:リモート画像のレンダリングエントリ。「SDKがビューを提供し、ホストがそれを配置する」 UIKitの場合はtravel.videoViewを取得し、SwiftUIの場合はOysterVideoView(travel:)を使用します。
  • 使用タイミング:ハンドルが作成されるとすぐに利用可能になります(繰り返しアクセスしても同じビューが返されます)。任意の階層にマウントでき、エンジンが準備できれば自動的にレンダリングされます。start() の前後どちらでマウントしても機能し、黒画面にはなりません。
  • 注:セッションが終了すると、SDKは自動的にレンダリングバインディングを解放します。必要に応じてビューを階層から削除してください。

イベントとステータス events / status / isEnded

  • 目的:eventsは状態変更 + エラーのプッシュストリームです。statusは観測可能な現在の外部ステータスです。isEndedは終端状態の同期的に読み取り可能なフラグです。
  • 使用タイミング:初期のステータスを見逃さないように、** start()の前にevents **の消費を開始することをお勧めします。
  • 注意:events への各アクセスは独立したストリームを返し、複数購読をサポートします。購読解除 = for await イテレーションの終了(または保持されている Task の破棄)を意味します。SwiftUI では、@StateObject/@ObservedObject を使用して status を直接監視できます(エラーは引き続き events を通じて通知されます)。§7 を参照してください。

start() / start(maxExperienceTimeSec:)

  • 目的:作成時にキャプチャした ticket を使用して、トラベル + RTC 参加設定と交換し、接続して再生します。成功すると、SDK は自動的にリアルタイム接続を確立し、内部ステータスポーリングを開始します。ステータスは events を通じて表面化されます。
  • パラメータ:maxExperienceTimeSec(オプション)— **このアドベンチャー体験の最大持続時間(秒単位)**をリクエストします。値はそのままサーバーに送信されます。許可される値、実際の有効持続時間、および自動終了のタイミングはすべてサーバーによって決定されます。SDKはローカルでの検証を行いません。nilを渡す(またはパラメータなしのstart()を呼び出す)と、サーバーのデフォルト持続時間が使用されます。**ディレクションモードではこのパラメータは無視されます。**時間が終了すると、サーバーが体験を終了し、ホストはeventsを介してended終了状態を受信します(サーバー側の終了と同じ、§7を参照)。
  • 戻り値:OysterStartTravelData(mode / version / encryptedTravelIdなど、§8を参照)。これによりインタラクションUIが決定されます。
  • エラー:401010/401011(クレデンシャルが無効/使用済み)、403002(ワールドの準備ができていない)、403007(サービス仕様が有効化されていない、例:acting)、429001/429002(同時実行制限/容量枯渇)、500001(リソース/サーバー障害)、103004(同時開始)。
  • ticketのワールドmodeは、initializeに渡されたmodelと一致している必要があります(呼び出し元の責任)。モードごとのモデルでは、各モデルが独自のゲートウェイルートを持ち、start()はticketを現在初期化されているモデルのルートに送信します。SDKはこれを事前に検証しませんし、検証することもできません****。modeは、このstart()呼び出し自体の応答(OysterStartTravelData.mode)によって提供されます。呼び出し前、SDKは不透明なticketとモデル名のみを保持しており、比較対象のモードはなく、モデル名からモードを推測することはサーバー固有の命名規則を推測することになり、SDKはそれを行いません。したがって、異なるモードのワールドに入る前に、一致するモデルを指定してinitialize()を再度呼び出してください(アイドル状態では最新の構成が優先され、cleanup()は不要でトークンは保持されます。Travelの実行中は呼び出しが無視されるため、事前にend()を実行してください)。不一致の場合、このstart()はゲートウェイで失敗します。診断時は、まず現在のmodelとticketのワールドのmodeが一致していることを確認し、上記の認証情報エラーコードを参照してください。
  • 注意(ストリームなし時の自動終了):サーバーは「ストリームなしタイムアウト」(デフォルト約 30 秒)を送信します。接続後、その期間内にストリームが受信されない場合(running に到達しない場合)、SDK は自動的に体験を終了し、failed に遷移し、events の .error を通じて 105006 を表面化させます(致命的。開始前の画面に戻ることで対応し、自分でタイミングを計る必要はありません)。

pause() / resume()

  • 目的:体験の一時停止/再開(冪等性あり)。
  • 使用タイミング:directing および acting ワールドでサポートされていますが、adventure ではサポートされていません。start() によって返される mode を使用して、一時停止ボタンを表示するかどうかを事前に決定します。pause は現在の状態が running である必要があります。resume は paused である必要があります。
  • エラー:103001(アクティブな体験なし)、103002(状態/バージョンが許可されていない)、103003(モードの不一致)。

rewind(toSec:)

  • 目的:指定された秒数まで巻き戻します。成功すると、SDKは元のrtcConfigで自動的に再参加し、再生に戻ります。
  • 使用タイミング:**** paused状態でのみ開始でき、directingワールドによってのみ実行できます。actingとadventureは巻き戻しをサポートしていないため、これらのモードでは巻き戻しエントリポイントを非表示にし、呼び出さないでください。
  • エラー: 103001、103002。

end()

  • 目的:体験を終了します(冪等性があり、繰り返し呼び出し可能)。呼び出しが成功するか、異常終了した後、SDK は自動的にリアルタイム接続を切断し、ポーリングを停止し、すべてのセッションリソースを解放します。同時に ticket が無効化され、ハンドルは終端状態に入ります。
  • 使用タイミング / 注意:ユーザーが能動的に終了する場合でも、体験が受動的に終了する場合でも(タイマーの満了、.ended/.failed の受信、ページの破棄)、必ず単一の end() に到達するようにしてください。そうしないと、リモートリソースが速やかに解放されない可能性があります。すべての終了パスを同じ冪等なクリーンアップメソッドに集約することをお勧めします。

sendInstruct(content:)(ディレクティングモード)

  • 目的:ストーリーを進行させるためのテキスト指示を送信します。
  • 使用タイミング:ディレクティングモード。runningのときは直接送信され、pausedのときはキャッシュされ、再接続によりrunningに再開した後の最初のフレームで再送信されます。
  • エラー:103001、103002、103003(アドベンチャーモードで呼び出された場合)、403004(コンテンツモデレーション)、404000(travelが見つからない)。

sendCommand(_:) / flushCommands()(アドベンチャーモード)

  • sendCommand:方向/視点/アクション制御コマンドを送信します(§8 OysterAdventureCommand を参照。ファイアアンドフォーゲット、戻り値なし、スローなし)。running の状態にある adventure モード でのみ有効です。外部入力は毎フレーム高頻度になる可能性があり、SDK は内部でスロットリングを行います(最新優先のサンプリング、RTC ラインレートでのフレームマージ)。ホスト側でスロットリングを行う必要はありません。サーバーのコマンドへの応答自体に遅延があるため、実際の有効時間は固定されていない点に注意してください。
  • flushCommands:「キーリリース/入力リリース」の瞬間に呼び出され、キュー内ですでに待機している最後のコマンドを即座に再送信します。保留中のコマンドがない場合は純粋な no-op となり、新しいコマンドは生成されません。
  • エラー(すべてthrowsではなくeventsの.errorを介して表面化):アクティブな体験なし103001、ディレクティングモード 103003で呼び出された、リアルタイムチャネルの準備ができていない / 送信失敗105004。

pauseLocalAudioCapture() / resumeLocalAudioCapture()

  • 目的:ローカルマイクキャプチャに対するSDKの占有を一時的に解放/復元します。
  • 使用タイミング:音声認識などがマイクへの排他アクセスを必要とする場合、まずpauseし、その後resumeします。

7. イベントとステータス

イベントは OysterTravel.events にアタッチされており、SDK からユーザーへのアクティブプッシュチャネルであり、独自の呼び出しによってトリガーされない状況(内部で管理されるリアルタイム接続やステータスポーリングの問題など)を表面化させるために使用されます。

var events: AsyncStream<OysterTravelEvent> { get }

@available(iOS 15.0, *)
public enum OysterTravelEvent {
    case statusChanged(OysterTravelStatus)
    // The SDK's internal flow errored, e.g. an internal API request error or a streaming error; note you must check for token expiration here and re-request the token
    case error(OysterSDKError)
}

消費スタイルは2種類あります。いずれかを選択してください:

  • SwiftUI:OysterTravelはObservableObjectです。@StateObject/@ObservedObjectを直接使用してstatusを監視し、UIを制御してください。エラーは引き続きeventsから発生します。
  • 命令型 / UIKit:Task内でfor await event in travel.events { ... }を実行し、.statusChanged / .errorに対してswitchを行います。完了したら保持しているTaskをキャンセルしてください。
let task = Task {
    for await event in travel.events {
        switch event {
        case .statusChanged(let status): render(status)   // see table below
        case .error(let error):          handle(error)     // error.code / error.kind, see §9
        }
    }
}
// When done: task.cancel()

OysterTravelStatus(5つのプロセス状態 + 2つの終端状態):

ステータス

説明

一般的な処理

idle

作成後、開始前

—

prepare

接続中 / 再接続中(内部の接続 / 再接続)

接続中 / 再接続中のヒントを表示

running

ストリーム準備完了、インタラクティブ(内部再生中)

画像とコントロールを表示

pausing

一時停止が受け付けられました、サーバーの確認待ち

「一時停止中…」を表示

paused

一時停止(確認済み)

一時停止状態を表示(directingのみが巻き戻しエントリを表示)

ended

終了(アクティブな終了またはサーバー側の終了)。終端

終了処理を行いページを閉じる

failed

失敗。終端

エラーを表示して終了処理

注記ended / failed に入ると、セッションは終了し、すべてのセッション操作(pause/resume/sendCommand…)はそれ以降効果を発揮しません。コールバックはメインスレッドでトリガーされる可能性があるため、UI を直接更新できます。

注記内部ステータスポーリング中、SDK はクライアントのプル/再生状態のハートビートを自動的に報告します(接続中/再生中/一時停止/再接続中/切断)。これにより、サーバーはストリーム側の状態とクライアント側の状態を区別できます。これは純粋に SDK 内部の動作であり、ホストが認識したり関与したりする必要はありません。

8. データモデル

注記すべてのパブリック型には @available(iOS 15.0, *) が付けられています。以下の戻り値/パラメータは SDK 出力 であり、ネイティブな Swift 型(Date / TimeInterval / OysterTravelStatus)を使用して内部でインプレース構築されます。これらは **** Codable ではなく、ワイヤ(snake_case)の詳細を公開しません。ワイヤのデコードは SDK 内部で行われます。

// Configuration
public struct OysterConfig: Sendable {
    public let apiHost: String             // Bailian gateway URL (not a business server), required, passed explicitly by the host
    public let model: String               // Versioned model name (e.g. "happyoyster-1.0-adventure"), required, no default
    public let logLevel: OysterLogLevel?   // .debug/.info/.warning/.error; defaults to .warning when nil
    public let callbackTimeoutMs: Int      // global callback timeout, default 30000; surfaced as 105005 on timeout
    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
}

// External session status (§7)
public enum OysterTravelStatus: String, Equatable, Sendable, CustomStringConvertible {
    case idle      // after create, before start
    case prepare   // connecting / reconnecting
    case running   // stream ready, interactive
    case pausing   // pause accepted, awaiting server confirmation
    case paused    // paused (confirmed)
    case ended     // terminal: active end or server-side end
    case failed    // terminal: failed
}

// Open string value (preserves unknown values for server-side extension; encoded/decoded as a bare JSON string "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() return (SDK output, not Codable)
public struct OysterStartTravelData: Equatable, Sendable {
    public let encryptedTravelId: String  // identifier for this experience
    public let encryptedWorldId: String
    public let mode: OysterModeValue       // adventure / directing / acting
    public let playUrl: String?            // currently always returned as null by the server
    public let firstFrame: String?         // first-frame image URL, may be null (produced asynchronously)
    public let version: String             // world version identifier (diagnostics; drive interaction UI from `mode`)
    public let aspectRatio: String?        // "9:16" / "16:9"; only set for acting worlds, nil otherwise
}

// Control API returns (SDK output, not Codable; status is the external enum 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 }

// Adventure-mode control command (strongly typed enums; values aligned with the internal 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   // movement for this command; submit every frame while held, then stop
    public let rotation: Rotation          // view rotation for this command; submit every frame while held, then stop
    public let interaction: Interaction    // one-shot action for this command; submit once
    public init(translation: Translation = .none, rotation: Rotation = .none, interaction: Interaction = .none)
    public init(_ params: WorldControlParams)   // convenient bridge from the default control WorldControlParams (consistent rawValue)
}

// Unified error (§9)
public struct OysterSDKError: Error {
    public let code: Int
    public let raw: Any?                  // the SDK's internal raw error info; structure is not guaranteed stable, for logging/diagnostics only
    public var kind: OysterErrorKind      // typed view of the numeric code, for exhaustive switch (computed property)
    public init(code: Int, raw: Any? = nil)
    // Error code constants are in the nested OysterSDKError.Code (e.g. .notInitialized = 100001)
}

// Typed semantic view of error codes: local codes are named cases; server-side 4xxxxx/5xxxxx converge to .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(wander) / directing(ストーリー) / acting(ロールプレイング)となります。OysterModeValueの定義が正式なものです。
  • 注:aspectRatioはオープンな文字列です(現在は9:16 / 16:9ですが、今後追加される可能性があります)。既知の値と一致させるのではなく、width:heightとしてパースして比率を比較してください。

9. エラーコード

SDK はエラーを一様に OysterSDKError として報告し、その型は常に code によって区別されます。「エラーがどのパスから来たか」で型を判定しないでください。同じ code がビジネスメソッド(async throws)によってスローされることもあれば、events の .error を通じて表面化されることもあります。型付きマッチングには error.kind を使用します(§8 OysterErrorKind を参照)。

エラーコード:サーバー 4xxxxx/5xxxxx、クライアントローカル 1xxxxx。

サーバーエラーコード(共通)

code

意味

推奨される処理

400000

無効なパラメータ(無効な列挙値など)

リクエストパラメータまたはSDKバージョンを確認

401010

体験クレデンシャル(ticket)が無効または期限切れです

サーバーに認証情報を再発行させる

401011

体験クレデンシャル(ticket)はすでに使用されています

使い捨ての認証情報、再発行が必要

403001

ワールドが存在しない、削除された、または現在の開発者に属していない(クレデンシャル発行後に削除されたワールドを含む)

有効なWorldを再度選択

403002

ワールドが準備未完了

開始前にワールドの準備が完了するのを待つ

403003

このAPIはプライマリAPIキーのみ許可します

このAPIには一時キーを使用できません

403004

コンテンツモデレーションによって入力コンテンツが拒否されました。sendInstructのテキスト指示に適用されます

入力を変更して再試行

403007

リクエストされたサービス仕様が有効になっていません

容量がいっぱいであるかのように再試行しないでください。有効な仕様に切り替えてください(通常:アカウントにacting仕様がない)

403008

キャパシティ構成が一時的に利用できません

後ほど再試行

404000

リソースが存在しません(world/wanderの所有権またはアーティファクトなし)

ID / ステータスの確認

409000

リクエストが現在のリソース状態と競合しています

体験の状態を確認

429001

この仕様の並行処理制限に達しました

既存のセッションが終了した後に再試行してください(500001と混同しないでください)

429002

利用可能なキャパシティが不足しています

後ほど再試行

500000

内部システムエラー

後ほど再試行 / 報告

500001

推論リソースの割り当てまたは内部サービス障害

後ほど再試行

クライアントローカルエラーコード

code

意味

SDKがセッションを自動終了

推奨される処理

100001

SDK初期化前に呼び出された場合。apiHost/modelが空白または無効であったために有効にならなかったinitializeも含まれます

いいえ(同期的にスロー、この呼び出しを拒否)

まずinitializeを実行し、apiHostとmodelの両方が正しく設定されていることを確認してください

101001

HTTP認証トークンが注入されていません

いいえ

updateToken後に再試行

101002

HTTP認証トークンが無効 / 拒否されました

いいえ

トークンを再交換してからupdateTokenを再試行してください

103001

現在アクティブな体験はありません

いいえ(この呼び出しを拒否)

まずcreateTravel + start

103002

現在の状態/バージョンではこの操作は許可されていません

いいえ(この呼び出しを拒否)

体験の状態 / versionを確認

103003

モードの不一致(例:非adventureワールドでsendCommandが呼び出された場合)

いいえ(この呼び出しを拒否)

modeに応じて適切なAPIを選択してください。§2の機能表を参照してください

103004

体験の同時作成 / 開始

いいえ(同期的にスロー)

呼び出しを直列化し、まず古いセッションをend

105001

リアルタイム接続に失敗

はい

終了して再開

105002

リアルタイム参加タイムアウト

はい

終了して再開

105003

最初のビデオフレーム待機中のタイムアウト

はい

終了して再開

105004

リアルタイムチャネルが準備未完了 / 送信失敗

場合による(アクティブな送信失敗、ハートビートは報告のみ)

running後に送信

105005

コールバックタイムアウト(デフォルト30秒)

いいえ

再試行し、必要に応じてcallbackTimeoutMsを増やしてください

105006

参加後にストリームがありません。SDKが体験を自動終了します

はい

終了して再開

106001

ローカルネットワークエラー

いいえ

再試行可能

106002

レスポンスの解析に失敗

依存する

SDKのアップグレード / 報告

106003

認識可能なエラーコードがない / プロキシ文字列エラーコード

いいえ

再試行可能

108001

サーバーの機能スイッチによってリモートで無効化されています(完全シャットダウンまたはバージョンが低すぎる。理由はrawを参照)

はい

rawの理由に従ってください。バージョンが低すぎる場合はユーザーにアップグレードを促してください

致命性の判定方法:致命性はブール型フィールドとしては公開されなくなりました(OysterSDKError には isFatal がありません)。意味的に、「fatal」は具体的にSDK がセッションを積極的に終了させるかどうか(RTC の切断、セッション全体の解放)を指します—

  • セッションを自動終了させるエラー(例:105001/105002/105003/105006/108001):ホストはこれをステートマシンの終端状態(status → failed、events の .statusChanged を通じて表面化)から認識し、それに応じて「体験開始」前の画面に戻ります。致命性自体を判定する必要はありません。
  • 呼び出し拒否エラー(100001/103001/103002/103003/103004):同期的にスローされる / 能動的に呼び出したときに呼び出しが拒否される。これらによってセッションが終了することはありません。
  • その他の非致命的エラー(例:101001/101002/105005/106001/106003):セッションは終了しません。提案に従って再試行するか、トークンを再注入後に続行してください。

注記**106001**には2つの原因が考えられます。ローカルネットワークエラー、または不正なapiHostです。再試行してもリクエストが回復しない場合は、apiHostが正しく設定されているか確認してください。

注記model を設定した後に 106003 が表示される場合、通常はモデル名/バージョンが間違っているか、アカウントで有効化されていないことを意味します。モデル、apiHost、およびトークンを一緒に確認してください。これらはすべて同じアカウントとリージョンに属している必要があります。不一致の場合、ゲートウェイは AccessDenied で拒否し、このコードに正規化されます。