HappyOyster Android SDK のエントリーポイントはシングルトンオブジェクト HappyOyster です。initialize、updateToken、attachVideo、sendCommand を除き、すべてのビジネスメソッドはサスペンド関数であり、失敗時に SDKError をスローします。アドベンチャー/演出/演技の3つのモード、必要なモデル設定、および aspectRatio に対応しています。
呼び出し元のコルーチンがキャンセルされた場合、SDKはその呼び出しに関連する進行中のHTTPリクエスト(存在する場合)をキャンセルし、それをSDKErrorに変換することなくCancellationExceptionとしてそのまま伝播させます。キャンセル操作を行っても、サーバーがすでに受け入れたリクエストがロールバックされることはありません。
統合フロー、インストール、ベストプラクティスについては、Happy Oyster Android SDK 統合ガイドを参照してください。
主要用語
用語 | 意味 |
|---|---|
token | Bailian ゲートウェイ API Key:アプリによって |
ticket | ワンタイム体験用認証情報:オープンプラットフォーム経由でサーバーによって交換され、クライアントに渡されます。単一の |
環境要件
項目 | 要件 |
|---|---|
minSdk | 24 (Android 7.0) 以上 |
compileSdk | 36 |
言語 | Kotlin(コルーチン |
ABI |
|
ネットワーク | パブリックインターネットアクセスが必要 |
主要概念
概念 | 説明 |
|---|---|
World | キャラクターやシーンを含む AI ワールドです。サーバーによって作成・管理されます。 |
Travel | 単一のリアルタイム体験です。基本的なライフサイクルは |
モード |
|
リアルタイムビデオ |
|
概要
HappyOyster メソッド
メソッド | 説明 |
|---|---|
| SDK を初期化します。アイドル状態での再初期化はサポートされています。Travel が開始中、アクティブ、または終了中の再初期化は |
| Bailian ゲートウェイ API キー(Bearer トークン)を注入または更新します。 |
| ワンタイムクレデンシャルを使用して体験を開始し、リアルタイム動画接続を自動的に確立します。 |
| 体験を非同期に一時停止します(ディレクティングおよびアクティング、かつ体験が一時停止をサポートする場合のみ)。受理後、実際の一時停止は |
| 一時停止中の体験を再開します(ディレクティングおよびアクティング)。内部的に 3 回のリトライバックオフを含みます。 |
| 指定された秒数まで巻き戻します(ディレクティングのみ、状態は |
| ナラティブを進行させるためのテキスト指示を送信します(ディレクティングおよびアクティング、 |
| 方向/視点/アクション制御コマンドを送信します(アドベンチャーモード、 |
| 再生用の |
| 体験を終了し、リアルタイム接続を自動的に切断してすべてのセッションリソースを解放します。 |
| SDK バージョン文字列(SemVer)、コンパイル時定数です。 |
イベント
イベント | 説明 |
|---|---|
| 体験ステータスの変更(リアルタイム動画のライフサイクルを含む)。値は |
| 内部自動フローが失敗した際のコールバックです。致命的なエラーの場合は現在の体験も終了します。 |
主要データ型
タイプ | 説明 |
|---|---|
| SDK 初期化設定です( |
| 体験ステータス: |
| 体験モード: |
| ワールド作成モデル: |
|
|
|
|
| SDK エラー。 |
HappyOyster.initialize
SDKを初期化します。他のどのAPIよりも前に呼び出してください(できればApplication.onCreate内で行います)。configは必須であり、アカウントのBailian apiHostと、happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventureのいずれかのmodel(Open APIのエントリーポイントと一致するもの)を含める必要があります。SDKは以下のようにリクエストURLを構築します。
https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}
modelにはデフォルト値がありません。SDKConfigの構築時にこれを省略するとコンパイルエラーになります。空の値を渡すと、initializeが同期的にSDKError(100002)をスローします。ランタイムの作成や置換は行われず、ネットワークリクエストも送信されません。SDKはActivityではなくアプリケーションコンテキストのみを保持します。再初期化は、前のランタイムがアイドル状態の場合にのみサポートされます。モデルを切り替えるには、アイドル中に新しいmodelで再初期化してください。Travelが開始中、アクティブ、または終了中の場合は、まずendTravel()を待機してください。
注記
地域のコンプライアンスに関するお知らせ:- 適用される法令およびデータ要件への準拠をサポートするため、対象ユーザーに米国ユーザーが含まれる場合、それらのユーザーに提供されるサービスについては、SDK 初期化時に米国リージョンの API Host を設定する必要があります。
- 開発者は正しい設定を行う責任があり、この要件に従わなかった場合は適用される法律に基づき相応の責任を負います。
シグネチャ
fun initialize(context: Context, config: SDKConfig)
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい |
|
|
| はい | SDK 設定。必須かつデフォルト値のない |
を返します
戻り値はありません。
エラー
code | 説明 |
|---|---|
|
|
| Travel が開始中、アクティブ、または終了中のため再初期化が拒否されました。 |
HappyOyster.updateToken
BailianゲートウェイAPI Keyを注入または更新します。スレッドセーフであり、initializeの後に呼び出し可能です。SDKはstartTravelおよびその後の体験制御リクエストに最新のトークンを使用します。ticketはワンタイムの体験用認証情報であり、Bearerトークンと混同しないでください。
シグネチャ
fun updateToken(token: String)
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい | Bailian ゲートウェイ API キー、Bearer トークンとして使用されます。 |
を返します
戻り値はありません。
エラー
code | 説明 |
|---|---|
| SDK が初期化されていません。 |
HappyOyster.startTravel
1回限りの ticket を使用して体験を開始します。成功すると、SDK は 自動的にリアルタイムビデオ接続を確立し、内部ステータスポーリングを開始 し、ステータスは onStatusChanged を通じて通知されます。
ticketはワンタイムクレデンシャルであり、呼び出しが行われた時点で消費済みとみなされます。- アプリごとに同時に実行できるTravelは、いかなる時点でも1つだけです。体験の進行中に再度呼び出すと
SDKError(103004)がスローされ、SDKError.rawには診断用に現在アクティブなチケットの要約のみが含まれ、完全なチケット情報は含まれません。 - オプションで
maxExperienceTimeSecを渡して、この体験の最大時間を制限できます(ワールド探索/アドベンチャーモードのみ)。パラメータを参照してください。
StartTravelData.creationModel(CreationModelValue、デフォルトはCreationModelValue.Simple):ワールドの作成モデル、つまりスクリプトコンテンツの管理方法を示します。simple(デフォルト)は通常のinstruct駆動型ワールドです。ワールド探索型のワールドもこの値に正規化されます。scriptlistは構造化されたScriptListワールドであり、そのスクリプトコンテンツはこのSDKを通じてアクセスされません。ScriptListモード(creationModel == CreationModelValue.ScriptList)でsendInstructを呼び出すと拒否され、SDKError(103002)がスローされます。このフィールドのデフォルト値はsimpleです。
StartTravelData.aspectRatio(String?):サーバーがこのセッションに割り当てた再生アスペクト比をwidth:height文字列として示します。nullでないのはActingのみであり、"9:16"(縦向き、作成時のサーバー側のデフォルト)または"16:9"(横向き)となります。ワールド探索およびdirectingモードではnullとなり、サーバーが値を報告しない場合もnullとなります。SDKは未知の値をそのまま保持し(nullに統合することはありません)、ホスト側は認識できない値をnullと同様に扱い、独自のデフォルトの向きにフォールバックする必要があります。SDK自体はこのフィールドを使用しません。
この値は startTravel() の戻り値とともに提供されます。startTravel() が返った後、かつ **** attachVideo() を呼び出して返されたビューをレイアウトに追加する前に、この値に基づいて再生コンテナのサイズを設定してください。SDK はホストがビューをアタッチして初めてリモートストリームのレンダリング用バインドを開始するため、その時点で向きを決定しても最初のレンダリングフレームより前に行われます(ただし、SDK がリアルタイムルームに参加する前に完了することは 保証されません)。リモートビューは clip-to-fill レンダーモードでバインドされるため、この値と向きの異なるコンテナを使用すると、レターボックスになる代わりに 画像がトリミングされます。
シグネチャ
suspend fun startTravel(ticket: String): StartTravelData
suspend fun startTravel(ticket: String, maxExperienceTimeSec: Int?): StartTravelData
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい | サーバーから発行されるワンタイム体験クレデンシャルです。 |
|
| いいえ | このエクスペリエンスの最大持続時間(秒単位)。この時間に達すると、ワールドはセッションを自動的に終了します。ワールド探索(アドベンチャー)モードにのみ適用されます。演出(リアルタイム演出)モードおよび演技モードでは無視されます。許可される値はサーバー側で設定されており、現在は |
を返します
StartTravelDataを返します。これには体験のメタデータ(mode、version、creationModel、aspectRatioなど)が含まれます。インタラクション方法(例:travel.mode、travel.creationModel、travel.version)を決定する前に、返されたメタデータを確認してください。Actingの場合は、travel.aspectRatioに基づいて再生コンテナのサイズも設定してください。
エラー
code | 説明 |
|---|---|
|
|
|
|
| 無効なパラメータ(例: |
| World が準備完了状態ではありません |
| リソース割り当て/内部サービス障害 |
| Travel がすでに開始中、アクティブ、または終了中であるか、あるいは |
HappyOyster.pauseTravel / HappyOyster.resumeTravel
体験を一時停止または再開します。SDK は一度に1つの Travel のみを管理します。encryptedTravelId は SDK 内部で取得されるため、呼び出し元が渡す必要はありません。
前提条件:
pauseTravel:ディレクティングまたはアクティング かつ状態がrunningの場合のみ呼び出し可能です。resumeTravel:ディレクティングまたはアクティング かつ状態がpausedの場合のみ呼び出し可能です。
すべての体験が一時停止/再開をサポートしているわけではありません。体験は、そのモードが必要とするバージョン識別子(StartTravelData.version—directingの場合はstoryV2、Actingの場合はactingV2。周囲の空白文字や大文字小文字を無視して比較されます)を報告する必要があります。そうでない場合、pauseTravelとresumeTravelの両方が103002を返します。Actingは一時停止/再開をサポートしますが、rewindTravelはサポートしていません。
一時停止は非同期です(重要):pauseTravel は「重い」操作であり、呼び出しの成功(メソッドの復帰)は 一時停止が受理された ことのみを意味し、その時点ではエクスペリエンスは 実際にはまだ一時停止されていません。一時停止が実際に有効になるのは、onStatusChanged コールバックが paused を報告したときだけです。 したがって、ホストの状態遷移や呼び出しの制御はそのコールバックから行ってください。「pauseTravel の呼び出し」と「paused コールバックの受信」の間は、ローカル状態を pausing としてマークできます。paused を受信した後でのみ、resumeTravel または rewindTravel の開始を許可してください。pauseTravel の戻り値をすでに一時停止済みとして扱わないでください。
リアルタイム接続の処理:実際に一時停止した後、SDKはリアルタイム接続を切断します。再開時、SDKは自動的に再接続し、同じstartTravel時に渡された認証情報を使用して映像を復元します。ホスト側の介入は不要です。
順序付けられた一時停止解体バリア:Pausedが発行された後でも、サーバー側のリアルタイムルームの解体にはわずかな遅延が生じる可能性があります。SDKは一時停止の確認から3秒の確定ウィンドウを設定します。このウィンドウ内でresumeTravelまたはrewindTravelが呼び出された場合、サスペンド中の呼び出しはまず残りの時間をノンブロッキングで待機し、その後ルーム再オープンのAPIリクエストを送信します。すでに3秒が自然に経過している場合は、遅延は追加されません。これにより、遅れた一時停止解体によって新しくオープンされたルームが閉じられ、105001が表面化することを防ぎます。
resume API retry:resumeTravel は、一時停止後の一時的なサービス利用不可に対処するため、失敗時に最大 3 回内部でリトライします(バックオフ 1 s / 2 s / 3 s)。3 回の試行すべてが失敗した場合にのみ、エラーが上位に伝播されます。
注記⚠️ 注意:頻繁な切り替えを避けるため、resumeTravelが正常にリターンした後、最大3秒待機してからpauseTravelを再度開始することを推奨します。これはホスト側の呼び出しクールダウンに関する推奨事項であり、SDK自体によって強制されるものではありません。詳細については、Happy Oyster Android SDK統合ガイドを参照してください。
シグネチャ
suspend fun pauseTravel(): TravelStateData
suspend fun resumeTravel(): TravelStateData
パラメータ
パラメータはありません。
を返します
TravelStateData(encryptedTravelId、status を含む)を返します。
エラー
code | 説明 |
|---|---|
| アクティブな体験がありません |
| 状態が許可されていないか、この体験は一時停止/再開をサポートしていません |
| アドベンチャーモードで呼び出されました。一時停止/再開をサポートするのはディレクティングとアクティングのみです |
HappyOyster.rewindTravel
指定された秒数まで巻き戻します。encryptedTravelId は SDK 内部で取得されます。
前提条件:directingモードかつ状態がpausedの場合にのみ呼び出し可能です(running状態では巻き戻しは許可されていません)。すべてのdirecting体験が巻き戻しをサポートしているわけではなく、サポートされていない場合は103002が返されます。巻き戻しが成功した後、体験は自動的に再開され、SDKも自動的にRTCに再接続するため、ホスト側の介入は不要です。
Actingには巻き戻し機能が一切ありません:Actingでこれを呼び出すと、SDKによってローカルで拒否され、103003が返されます。リクエストは発行されません。Actingでは、ホスト側でボタンを単に無効化するのではなく、巻き戻しのエントリーポイントを非表示にするべきです。
シグネチャ
suspend fun rewindTravel(rewindToSec: Double): RewindTravelData
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい | 巻き戻す目標秒数です。4 の倍数 である必要があります(例:4、8、12)。4 の倍数でない値は、サーバーによって最も近い小さい 4 の倍数に切り捨てられます(例:7 を渡すと 4 になります)。 |
を返します
RewindTravelData(encryptedTravelId、status、resumedAtSec を含む)を返します。ここで resumedAtSec はサーバーが実際に巻き戻した秒数です(すでに 4 の倍数に切り捨てられています)。
エラー
code | 説明 |
|---|---|
| アクティブな体験がありません |
| 状態が |
| アドベンチャーまたはアクティングモードで呼び出されました。巻き戻しをサポートするのはディレクティングモードのみです |
HappyOyster.sendInstruct
ナラティブを推進するためのテキスト指示を送信します。directing or Acting であり、体験ステータスが running または paused の場合に有効です。encryptedTravelId は SDK 内部で取得されます。
一時停止状態での動作:SDKは一時停止中に自動的には再開しません。instructは直接送信されます。instructを送信する前にresumeTravelを先に呼び出すかどうかは、ホスト側の判断に委ねられます。
シグネチャ
suspend fun sendInstruct(content: String): SendInstructData
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい | 送信するテキスト指示の内容。 |
を返します
SendInstructData(encryptedTravelId、content、accepted を含む)を返します。
エラー
code | 説明 |
|---|---|
| アクティブな体験がありません( |
| 現在のモードはディレクティングでもアクティングでもありません(アドベンチャーモードで呼び出されました) |
| 体験の状態が |
| コンテンツモデレーションによるブロック |
| Travel が存在しません |
HappyOyster.sendCommand
方向/視点/アクション制御コマンドを送信します。アドベンチャーモード かつ running の場合のみ有効です。
- メインスレッドで呼び出してください。メインスレッド外での呼び出しは同期的に
IllegalStateExceptionをスローします。 - アクティブな体験がない場合は
103001が報告されます。 - アドベンチャーモード外(ディレクティング/アクティング)で呼び出すと
103003が報告されます。 - 許可されていない状態の場合は
103002が報告されます。 - アップリンクはサイレントオーディオストリームを介して確立されます(SDKは実際の音声を録音またはアップロードしません)。DataChannelにとって
RECORD_AUDIOは厳密な前提条件ではありませんが、クロスデバイスの互換性のためにこれを宣言して許可することが推奨されます(Happy Oyster Android SDK統合ガイド § インストール・権限 を参照)。105004は、リアルタイムチャネルの準備ができていない、または送信に失敗したことを意味します(例:DataChannel接続の中断やリアルタイムチャネルの異常)。これは権限の欠如によって直接引き起こされるものではありません。
組み込みの 42 ms スロットリング(24 fps、最新優先):sendCommand は内部で DataChannel への書き込みを 42 ms 間隔(約 24 fps)でスロットリングします。状態検証は 同期的かつ即座に 実行され、不正な呼び出しの場合は直ちに例外をスローしますが、実際の DataChannel 書き込みは非同期にスロットリングされます。前回の送信から少なくとも 42 ms が経過している場合、コマンドは即座にディスパッチされます(初回呼び出しを含む)。それ以外の場合、保留中のコマンドは最新の値に置き換えられ、現在の 42 ms ウィンドウの終了時に一度だけディスパッチされます。したがって、1つのウィンドウ内での複数回の呼び出しは、最後の値を含む1回のネットワーク書き込みとなります。ホストは独自のレートリミッターを実装することなく、ゲームのフレームレートで sendCommand を呼び出すことができます。
スロットリング中の送信失敗:スロットリングされたフラッシュ処理は非同期であるため、送信失敗を呼び出し元にスローすることはできません。エラーはonErrorコールバックを通じて通知されます(非致命的、105004)。保留中のコマンドはセッション終了時に破棄されます(セッション終了後に送信されることはありません)。
AdventureCommand のフィールドと値:
フィールド | セマンティクス | 値 |
|---|---|---|
| 移動:前/左/後/右/斜め/待機 |
|
| 視点:上/下/左/右/斜め/none |
|
| インタラクション:ジャンプ/攻撃/しゃがむ/ダッシュ/none |
|
これら3つのフィールドは、互いに排他的な独立したコマンドグループです。斜め移動や視線移動には単一の組み合わせ値を使用します(例えば、前+左は同じフィールド内でWとAを同時に指定するのではなく、W_Aとなります)。各呼び出しでは、現在の完全な状態を渡す必要があります。
42 ms の間隔をメンタルモデルとして使用してください。
-
One-shot action(例:ジャンプ/攻撃のタップ、または1歩歩く):call once。SDK は最も近い間隔でそれを送信します。繰り返しの呼び出しや後続の
Noneコマンドは不要です。// Jump once HappyOyster.sendCommand(AdventureCommand("None", "None", "Jump")) -
長押しアクション(例:移動や回転の継続):長押し中は毎フレーム呼び出してください。SDKは約42ミリ秒ごとに1つのコマンドを発行します。リリース時には、状態をリセットするために
Noneを含むコマンドを明示的に1回送信してください。SDKが自動的にリセットコマンドを生成することはありません。// While held: call from the host's frame loop HappyOyster.sendCommand(AdventureCommand("W", "None", "None")) // On release: explicitly reset once HappyOyster.sendCommand(AdventureCommand("None", "None", "None"))
シグネチャ
fun sendCommand(command: AdventureCommand)
パラメータ
フィールド | タイプ | 必須 | 説明 |
|---|---|---|---|
|
| はい |
|
を返します
戻り値なし(同期)。状態検証は同期的かつ即座に実行されます。DataChannel への書き込みは非同期にスロットリングされます。
エラー
code | 説明 |
|---|---|
| アクティブな体験がありません |
| 許可されていない状態 |
| アドベンチャーモード外(ディレクティング/アクティング)で呼び出されました |
| スロットリングによるフラッシュ送信失敗(非致命的、非同期コールバック) |
HappyOyster.attachVideo
再生用の SurfaceView を返します。これをレイアウトに追加してください。SDK は内部でリモートストリームとのレンダリングバインディングを完了します。
- メインスレッドで呼び出してください。メインスレッド外での呼び出しは同期的に
IllegalStateExceptionをスローします。 - SDK は返された View への弱参照のみを保持し、体験終了時にレンダリングバインディングを解放します。View をレイアウトから削除するのは開発者の責任です。
- Acting体験の場合、返されたビューをレイアウトに追加する前に、
StartTravelData.aspectRatioに基づいて再生コンテナのサイズを設定してください。レンダリングはclip-to-fillモードにバインドされているため、向きが一致しないと画像がトリミングされます(startTravelを参照)。
シグネチャ
fun attachVideo(): SurfaceView
パラメータ
パラメータはありません。
を返します
SurfaceView を返します。これをレイアウトに追加してリアルタイム動画を再生してください。
エラー
code | 説明 |
|---|---|
| SDK が初期化されていません |
HappyOyster.endTravel
体験を終了します。encryptedTravelIdはSDK内部で取得されます。呼び出しが成功した後(または異常終了後)、SDKは自動的にリアルタイム接続を切断し、内部ポーリングを停止し、すべてのセッションリソースを解放します。同時に、現在のticketも無効化されます。
シグネチャ
suspend fun endTravel(): EndTravelData
パラメータ
パラメータはありません。
を返します
EndTravelData(encryptedTravelId、status、endedAt、durationSec を含む)を返します。
エラー
code | 説明 |
|---|---|
| SDK が初期化されていません |
| アクティブな体験がありません |
HappyOyster.VERSION
SDK バージョン文字列(SemVer)を返します(例:"x.y.z")。この値は VERSION_NAME Gradle プロパティによって注入されるコンパイル時定数であり、initialize を先に呼び出さなくても安全に読み取ることができます。
シグネチャ
val VERSION: String
を返します
SDK バージョン文字列(例:"x.y.z")。
Log.d("MyApp", "SDK version: ${HappyOyster.VERSION}")
イベントリスニング
interface HappyOysterListener {
fun onStatusChanged(status: TravelStatusValue) {}
fun onError(error: SDKError) {}
}
fun addListener(listener: HappyOysterListener)
fun removeListener(listener: HappyOysterListener)
SDK イベントは、あなたへの proactive push channel であり、あなた自身の明示的な呼び出しによってトリガーされない状況(例:SDK が自動的に維持するリアルタイム接続やステータスポーリングの問題)を報告するために使用されます。
イベント | 説明 |
|---|---|
| 体験ステータスの変更(リアルタイム動画のライフサイクルを含む)。値は |
| 内部自動フローが失敗した際のコールバックです。致命的なエラーの場合は現在の体験も終了します(エラーコードセクションを参照)。 |
注記⚠️ 注意:addListener / removeListenerはinitializeの後に呼び出す必要があります。初期化前にこれらを呼び出すとSDKError(100001)がスローされます。リスナーの登録は、HappyOyster.initialize(...)が正常にリターンした直後に行うことを推奨します。
データモデル
// Configuration
data class SDKConfig(
// Required: your account's Bailian API Host (e.g., llm-xxxx.ap-southeast-1.maas.aliyuncs.com), copied from the API Key page in the Bailian console;
// must belong to the same account/region as the injected API Key, otherwise the gateway returns AccessDenied.
val apiHost: String,
// Required with no default: the complete HappyOyster model name and version (e.g., happyoyster-1.0);
// refer to the official Bailian HappyOyster model documentation for available values.
val model: String,
// Minimum level for the built-in Logcat sink (does not affect logHandler); defaults to INFO, which
// includes the lifecycle anchors (initialize, Travel start/status/end, RTC connect/first-frame) needed
// to reconstruct a session timeline. Drop to WARN for errors-only, or raise to DEBUG/VERBOSE to diagnose.
val logLevel: LogLevel = LogLevel.INFO,
// Timeout for RTC join (timeout triggers 105002), first-frame wait (timeout triggers 105003), and SDK gateway HTTP signaling calls (timeout triggers 105005);
// the two RTC phases are serial, so the worst-case wait is 2×callbackTimeoutMs (60s).
val callbackTimeoutMs: Long = SDKConfig.DEFAULT_CALLBACK_TIMEOUT_MS, // 30_000ms
// Whether the SDK writes its own logs to Android Logcat (tag HappyOysterSDK); defaults to false (silent).
val logcatEnabled: Boolean = false,
// Host log callback; receives the full stream of SDK LogRecords, unaffected by logLevel; defaults to null.
val logHandler: HappyOysterLogHandler? = null,
) {
companion object {
/** Default callback timeout (milliseconds). Public constant, usable for comparison or display. */
const val DEFAULT_CALLBACK_TIMEOUT_MS: Long = 30_000
}
}
enum class LogLevel { VERBOSE, DEBUG, INFO, WARN, ERROR, NONE }
// Host log sink; injected via SDKConfig.logHandler; full firehose, unaffected by logLevel.
fun interface HappyOysterLogHandler {
fun onLog(record: LogRecord)
}
// SDK structured log record; delivered to HappyOysterLogHandler; contains no sensitive values (Bearer /
// ticket / RTC token, RTC identity fields, and media URLs are all redacted). travelId (encryptedTravelId)
// is kept in full so it can be correlated with server-side session logs.
data class LogRecord(
val level: LogLevel,
val tag: String, // fixed as "HappyOysterSDK"
val message: String, // readable log line (includes event name and details)
val throwable: Throwable?,
val timestampMs: Long, // epoch milliseconds at emission time
)
// Status and mode (unknown values are preserved to allow service extension)
@JvmInline value class TravelStatusValue(val rawValue: String) {
companion object {
val Init = TravelStatusValue("init")
val Pending = TravelStatusValue("pending")
val Running = TravelStatusValue("running")
val Paused = TravelStatusValue("paused")
val Failed = TravelStatusValue("failed")
val Completed = TravelStatusValue("completed")
}
}
@JvmInline value class ModeValue(val rawValue: String) {
companion object {
val Adventure = ModeValue("adventure")
val Directing = ModeValue("directing")
val Acting = ModeValue("acting")
}
}
@JvmInline value class CreationModelValue(val rawValue: String) {
companion object {
val Simple = CreationModelValue("simple") // default; prompt/instruct-driven, world-exploration worlds normalize to this
val ScriptList = CreationModelValue("scriptlist") // structured ScriptList world; sendInstruct not supported (throws 103002)
}
}
// startTravel return
data class StartTravelData(
val encryptedTravelId: String, // identifier for subsequent control interfaces
val encryptedWorldId: String,
val mode: ModeValue, // adventure / directing / acting
val creationModel: CreationModelValue = CreationModelValue.Simple, // world creation model; Simple (default) is instruct-driven, ScriptList does not support sendInstruct
val playUrl: String?,
val firstFrame: String?, // first-frame image address, may be produced asynchronously
val bgmUrl: String?,
val version: String, // world version identifier; Acting is actingV2
val aspectRatio: String? = null, // Acting only: 9:16 / 16:9; other modes null. Use to set player orientation
val maxExperienceTimeSec: Int? = null, // server-reported max experience time (seconds); non-null only for adventure; null for directing / acting
)
// Control interface parameters and returns
data class AdventureCommand(
val translation: String, // movement: forward/left/back/right/diagonal (W_A, …)/idle
val rotation: String, // view: up/down/left/right/diagonal (Mouse_Up_Left, …)/none
val interaction: String, // interaction: jump/attack/squat/sprint/none
)
data class TravelStateData(val encryptedTravelId: String, val status: TravelStatusValue)
data class RewindTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val resumedAtSec: Double)
data class EndTravelData(val encryptedTravelId: String, val status: TravelStatusValue, val endedAt: String, val durationSec: Int)
data class SendInstructData(val encryptedTravelId: String, val content: String, val accepted: Boolean)
// Error (identified by code; the original information is in raw)
// Note: SDKError is not a data class (no copy()/destructuring), it is a plain class.
class SDKError(val code: Int, val raw: Any? = null) : Exception("Happy Oyster SDK error: $code")
エラーコード
エラーは数値の code によって識別されます。SDK は呼び出し元が処理可能なビジネスエラーコード(一般的に 4xxxxx / 5xxxxx)をパススルーします。ローカル SDK エラーコードは 1xxxxx です。
ビジネスエラーコード(共通)
code | 意味 | 推奨対応 |
|---|---|---|
| 無効なパラメータ(無効な列挙型など) | リクエストパラメータまたは SDK バージョンを確認してください |
|
| サーバーにクレデンシャルの再発行を依頼してください |
|
| クレデンシャルは使い捨てです。再発行してください。 |
| ワールドが存在しない、削除済み、または現在の開発者に属していません( | 有効な World を再選択してください |
| World が準備完了状態ではありません | 開始前に World の準備が整うまでお待ちください |
| 入力コンテンツ違反(コンテンツモデレーション)。 | 入力内容を変更して再試行してください |
| このアカウントではサービス仕様が有効化されていません | 容量不足として再試行しないでください。有効な仕様に切り替えるか、有効化をリクエストしてください |
| キャパシティ設定が一時的に利用できません | 後で再試行 |
| Travel リソースが存在しないか、ID が現在のアカウントに属していません | Travel を再起動 |
| リクエストが現在のリソース状態と競合しています | Travel の状態を確認してください |
| SKU 同時実行制限に達しました | 既存のセッション終了後に再試行してください( |
| 現在利用可能なキャパシティがありません | 後で再試行 |
| 推論リソース割り当て/内部サービス障害 | 後で再試行 |
| 内部システムエラー | 後で再試行/報告 |
クライアント側ローカルエラーコード
code | 意味 | 致命的? | 推奨対応 |
|---|---|---|---|
| SDK の初期化前に呼び出されました | 呼び出し拒否 | 最初に |
| SDK 初期化時に | 初期化失敗 | 空でない完全なモデル名とバージョンを渡し、再度 |
| Bailian ゲートウェイ API Key が注入されていません | いいえ |
|
| BailianゲートウェイのBearer API Keyの有効期限切れ、または拒否されました。注意:これはゲートウェイのBearer Keyであり、ワンタイムチケットではありません。チケットレベルの認証エラーは、6桁のサーバーコード(例: | いいえ | Bearer キーを再取得して |
| 現在アクティブな体験はありません | 呼び出し拒否 | 最初に |
| 現在の状態ではこの操作は許可されていません(以下の状況を含みます:状態の不一致、体験が | 呼び出し拒否 | 体験の状態とモードを確認してください。ScriptList モード( |
| モードの不一致: | 呼び出し拒否 | 現在のモードがインターフェースの要件に合致しているか確認してください |
| 並行した | 呼び出し拒否 | 再試行前に |
| リアルタイム接続に失敗しました | はい | 終了して再起動 |
| リアルタイム参加がタイムアウトしました | はい | 終了して再起動 |
| ビデオの最初のフレーム待機中にタイムアウトしました | はい | 終了して再起動 |
| リアルタイムチャンネルの準備未完了/送信失敗 | いいえ | リアルタイムチャネルの準備ができていることを確認し、 |
| SDK ゲートウェイ HTTP シグナリング呼び出しが | いいえ | 再試行を推奨します。または |
| ストリームなしによる自動終了:最初のフレームが届かなかったか、実行中にストリームが中断され、タイムアウト前に回復しませんでした。SDK は現在の体験を能動的に終了します | はい | 終了して再起動 |
| ローカルネットワークエラー | いいえ | リトライ可能 |
| レスポンスの解析に失敗しました | いいえ | 明示的な呼び出しによってトリガーされた場合は呼び出し元にスローされます。内部ステータスポーリング時は |
| アップストリームサービスが認識できないエラーレスポンスを返しました。SDK は元の情報を | いいえ | 再試行可能です。問題が継続する場合は、 |
| SDK がリモートで無効化されています(完全停止またはバージョン过低)。理由は | はい(SDK が無効化結果を検出すると、進行中の体験を自動的に終了します。復旧後に再起動可能です) |
|
注記「Fatal?」は、具体的にはそれがSDKに現在の体験を自動終了させるかどうかを指します。100001/100002/103xxxのような同期検証エラーは、その呼び出しに対してのみ拒否またはスローを行い(100002は初期化を直接失敗させます)、体験を終了させることはありません。
致命的エラーと非致命的エラー:
- 致命的エラー:SDK は現在のエクスペリエンスを自動的に終了し(リアルタイム接続の切断、リソースの解放、
endTravelの呼び出し)、onErrorを通じて通知します。ホストは現在のエクスペリエンス状態をクリーンアップし、再起動を可能にする必要があります。カテゴリは次の4つです:① 内部ステータスポーリングがエクスペリエンスステータスとしてfailedを読み取った場合(例:500001推論失敗);② リアルタイム接続の致命的エラー(105001/105002/105003);③ ストリームなしによる自動終了(105006:最初のフレームが到達しない、または実行中にストリームが中断され、タイムアウト前に回復せず、SDK が能動的に終了させる場合);④ SDK がリモートで無効化された場合(108001)。 - 非致命的エラー:エクスペリエンスを終了させず、
onErrorを通じてのみ通知されます。updateTokenを再実行するか、サービス/リアルタイムチャネルの復旧を待ってから続行することができます。内部ステータスポーリング自体の単一リクエスト失敗(ネットワーク106001、パース106002、上流異常106003、ビジネスエラー5xxxxx)もこのカテゴリに含まれます。ポーリングは次のサイクルで継続され、ステータスがfailedと読み取られた場合にのみエクスペリエンスが終了します。認証失敗101001や非同期のsendCommandスロットリングフラッシュ送信失敗105004も同様に非致命的です。注:SDK はリアルタイムチャネル経由で独自のキープアライブペイロードを送信しないため、セッションがアイドル状態の間は105004が発生することはありません。これはユーザー自身のsendCommand呼び出しによってのみトリガーされます。