The entry point for the HappyOyster Android SDK is the singleton object HappyOyster . Except for initialize , updateToken , attachVideo , and sendCommand , all business methods are suspend functions that throw SDKError on failure. Covers the three modes adventure / directing / acting, the required model config, and aspectRatio.
CancellationException unchanged rather than converting it to SDKError. Cancellation does not roll back a request that the server has already accepted.
For the integration flow, installation, and best practices, see Happy Oyster Android SDK Integration Guide.
Core Terminology
Term | Meaning |
|---|---|
token | QwenCloud gateway API Key: injected as a Bearer token by your app via |
ticket | One-time experience credential: exchanged by your server via the open platform and delivered to the client, used only for a single |
Environment Requirements
Item | Requirement |
|---|---|
minSdk | 24 (Android 7.0) and above |
compileSdk | 36 |
Language | Kotlin (coroutine |
ABI |
|
Network | Public internet access required |
Core Concepts
Concept | Description |
|---|---|
World | An AI world, containing characters and scenes. Created and managed by your server. |
Travel | A single real-time experience. Basic lifecycle: |
Mode |
|
Real-time video | Established and maintained automatically by the SDK after |
Overview
HappyOyster methods
Method | Description |
|---|---|
| Initializes the SDK. Idle re-initialization is supported; re-initialization while a Travel is starting, active, or ending is rejected with |
| Injects/updates the QwenCloud gateway API Key (Bearer token). |
| Starts an experience with a one-time credential and automatically establishes the real-time video connection. |
| Asynchronously pauses the experience (directing and Acting, and only if the experience supports pausing); after acceptance, the actual pause is determined by |
| Resumes a paused experience (directing and Acting); includes 3× retry backoff internally. |
| Rewinds to the specified number of seconds (directing only, state |
| Sends a text instruction to drive the narrative (directing and Acting, |
| Sends direction/view/action control commands (adventure mode, |
| Returns a |
| Ends the experience, automatically disconnecting the real-time connection and releasing all session resources. |
| SDK version string (SemVer), a compile-time constant. |
Events
Event | Description |
|---|---|
| Experience status change (including the real-time video lifecycle); values are defined in |
| Callback when an internal automatic flow fails; a fatal error also terminates the current experience. |
Key data types
Type | Description |
|---|---|
| SDK initialization configuration ( |
| Experience status: |
| Experience mode: |
| World creation model: |
| Returned by |
| Parameter for |
| SDK error; contains |
HappyOyster.initialize
Initializes the SDK; call it before any other API (preferably in Application.onCreate). config is required and must carry your account's QwenCloud apiHost and a model of happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure (matching the Open API entry). The SDK constructs request URLs as follows:
model has no default value: omitting it when constructing SDKConfig is a compile-time error. Passing a blank value makes initialize synchronously throw SDKError(100002); no runtime is created or replaced and no network request is made. The SDK holds only the application context, not an Activity. Re-initialization is supported only while the previous runtime is idle. To switch models, re-initialize with the new model while idle; if a Travel is starting, active, or ending, await endTravel() first.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | Passing |
|
| Yes | SDK configuration; must carry the required, default-free |
Returns
No return value.
Errors
code | Description |
|---|---|
|
|
| Re-initialization rejected because a Travel is starting, active, or ending. Await |
HappyOyster.updateToken
Injects/updates the QwenCloud gateway API Key. Thread-safe and callable after initialize; the SDK uses the latest token for startTravel and subsequent experience-control requests. The ticket is a one-time experience credential and should not be mixed up with the Bearer token.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | The QwenCloud gateway API Key, used as the Bearer token. |
Returns
No return value.
Errors
code | Description |
|---|---|
| SDK has not been initialized. |
HappyOyster.startTravel
Starts an experience with a one-time ticket. On success, the SDK automatically establishes the real-time video connection and begins internal status polling, with status surfaced via onStatusChanged.
ticketis a one-time credential and is considered consumed once the call is made.- Only one concurrent Travel is allowed per app at any given moment; calling again while an experience is in progress throws
SDKError(103004), withSDKError.rawcontaining only a redacted summary of the currently active ticket for diagnosis, never the full ticket. - Optionally pass
maxExperienceTimeSecto cap the maximum duration of this experience (world-exploration / adventure mode only); see Parameters.
StartTravelData.creationModel (CreationModelValue, default CreationModelValue.Simple): indicates the world's creation model, i.e., how script content is managed. simple (default) is an ordinary instruct-driven world; world-exploration worlds are also normalized to this value. scriptlist is a structured ScriptList world—its script content is not accessed through this SDK. In ScriptList mode (creationModel == CreationModelValue.ScriptList), calling sendInstruct is rejected and throws SDKError(103002). This field defaults to simple.
StartTravelData.aspectRatio (String?): the playback aspect ratio the server assigned to this session, as a width:height string. Non-null only for Acting—"9:16" (portrait, the server-side default at creation) or "16:9" (landscape); null for world-exploration and directing modes, and null when the server does not report the value. The SDK preserves unknown values verbatim (it does not collapse them to null), so hosts must treat any unrecognized value like null and fall back to their own default orientation; the SDK itself does not consume this field.
The value is delivered with the startTravel() return value: size the playback container from it after startTravel() returns and before calling attachVideo() and adding the returned view to your layout—the SDK only starts binding the remote stream for rendering once the host attaches the view, so deciding the orientation at that point still precedes the first rendered frame (it is, however, not guaranteed to happen before the SDK joins the real-time room). The remote view is bound with a clip-to-fill render mode, so a container whose orientation disagrees with this value crops the picture instead of letterboxing it.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | The one-time experience credential, delivered by your server. |
|
| No | Maximum duration of this experience in seconds; the world ends the session automatically once it is reached. Applies to world-exploration (adventure) mode only; ignored in directing (real-time directing) and Acting modes. The allowed values are server-configured, currently |
Returns
Returns StartTravelData, containing experience metadata (mode, version, creationModel, aspectRatio, etc.). Inspect the returned metadata before deciding how to interact (e.g., travel.mode, travel.creationModel, travel.version); for Acting, also size the playback container from travel.aspectRatio.
Errors
code | Description |
|---|---|
|
|
|
|
| Invalid parameter (e.g. |
| World not in a ready state |
| Resource allocation / internal service failure |
| A Travel is already starting, active, or ending, or |
HappyOyster.pauseTravel / HappyOyster.resumeTravel
Pause / resume the experience. The SDK manages only one Travel at a time; encryptedTravelId is obtained internally by the SDK and need not be passed by the caller.
Preconditions:
pauseTravel: callable only in directing or Acting and when the state isrunning.resumeTravel: callable only in directing or Acting and when the state ispaused.
StartTravelData.version—storyV2 for directing, actingV2 for Acting; compared ignoring surrounding whitespace and letter case), otherwise both pauseTravel and resumeTravel return 103002. Acting supports pause / resume but does not support rewindTravel.
Pause is asynchronous (important): pauseTravel is a "heavy" operation—a successful call (method return) only means the pause has been accepted; at that moment the experience is not actually paused yet. The pause is only real once the onStatusChanged callback reports paused. Therefore, drive the host state machine and call gating from that callback: between "calling pauseTravel" and "receiving the paused callback" you may mark the local state as pausing; only after receiving paused should you allow initiating resumeTravel or rewindTravel. Do not treat the return of pauseTravel as already paused.
Handling of the real-time connection: after an actual pause, the SDK disconnects the real-time connection; on resume, the SDK automatically rejoins and restores the picture using the credential delivered during the same startTravel, with no host intervention required.
Ordered pause-teardown barrier: after Paused is emitted, server-side real-time-room teardown may still lag briefly. The SDK establishes a 3-second settle window from pause confirmation. If resumeTravel or rewindTravel is called inside that window, the suspending call first waits non-blockingly for the remaining time, then sends the room-reopening API request. No delay is added if 3 seconds have already elapsed naturally. This prevents a late pause teardown from closing the newly opened room and surfacing 105001.
resume API retry: resumeTravel internally retries up to 3 times on failure (backoff 1 s / 2 s / 3 s) to handle brief service unavailability after a pause; only if all 3 attempts fail is the error propagated upward.
resumeTravel returns successfully before initiating pauseTravel again, to avoid switching too frequently. This is a host-side call cooldown recommendation and is not enforced by the SDK itself. See Happy Oyster Android SDK Integration Guide for details.Signature
Parameters
No parameters.
Returns
Returns TravelStateData (contains encryptedTravelId, status).
Errors
code | Description |
|---|---|
| No active experience |
| State not allowed, or this experience does not support pause / resume |
| Called in adventure mode; only directing and Acting support pause / resume |
HappyOyster.rewindTravel
Rewinds to the specified number of seconds. encryptedTravelId is obtained internally by the SDK.
Preconditions: callable only in directing mode and when the state is paused (rewinding is not allowed in the running state). Not all directing experiences support rewinding, and unsupported ones return 103002. After a successful rewind, the experience resumes automatically and the SDK automatically reconnects RTC, with no host intervention required.
Acting has no rewind capability at all: calling it in Acting is rejected locally by the SDK with 103003, and no request is issued. In Acting, hosts should hide the rewind entry point rather than merely disabling the button.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | The target number of seconds to rewind to, which should be a multiple of 4 (e.g., 4, 8, 12). A non-multiple of 4 is floored by the server down to the nearest smaller multiple of 4 (e.g., passing 7 yields 4). |
Returns
Returns RewindTravelData (contains encryptedTravelId, status, resumedAtSec), where resumedAtSec is the number of seconds the server actually rewound to (already floored to a multiple of 4).
Errors
code | Description |
|---|---|
| No active experience |
| State is not |
| Called in adventure or Acting mode; only directing mode supports rewinding |
HappyOyster.sendInstruct
Sends a text instruction to drive the narrative. Valid in directing or Acting and when the experience state is running or paused. encryptedTravelId is obtained internally by the SDK.
Behavior in the paused state: the SDK does not automatically resume while paused; the instruct is sent directly. It is up to the host to decide whether to call resumeTravel first before sending the instruct.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | The text instruction content to send. |
Returns
Returns SendInstructData (contains encryptedTravelId, content, accepted).
Errors
code | Description |
|---|---|
| No active experience (never called |
| Current mode is neither directing nor Acting (called in adventure mode) |
| The experience state is neither |
| Content moderation block |
| Travel does not exist |
HappyOyster.sendCommand
Sends direction/view/action control commands. Valid only in adventure mode and when running.
- Call on the main thread; an off-main call synchronously throws
IllegalStateException. - No active experience reports
103001. - Calling outside adventure mode (directing / Acting) reports
103003. - A disallowed state reports
103002. - The uplink is established via a silent audio stream (the SDK does not record or upload real audio);
RECORD_AUDIOis not a hard prerequisite for the DataChannel, but declaring and granting it is recommended for cross-device compatibility (see Happy Oyster Android SDK Integration Guide § Installation · Permissions).105004means the real-time channel is not ready / a send failed (e.g. a DataChannel connection interruption or a real-time channel anomaly); it is not triggered directly by a missing permission.
sendCommand internally throttles DataChannel writes in 42 ms intervals (about 24 fps). State validation runs synchronously and immediately, throwing immediately on an illegal call; the actual DataChannel write is throttled asynchronously. If at least 42 ms has passed since the previous send, the command is dispatched immediately (including the first call). Otherwise, the pending command is replaced with the latest value and dispatched once at the end of the current 42 ms window. Multiple calls in one window therefore produce one on-wire write containing the last value. The host may call sendCommand at the game frame rate without implementing its own rate limiter.
Send failures during throttling: the throttled flush is asynchronous, and a send failure cannot be thrown to the caller—the error is surfaced via the onError callback (non-fatal, 105004). Pending commands are discarded when the session ends (they will not be sent after the session ends).
AdventureCommand fields and values:
Field | Semantics | Values |
|---|---|---|
| Movement: forward/left/back/right/diagonal/idle |
|
| View: up/down/left/right/diagonal/none |
|
| Interaction: jump/attack/squat/sprint/none |
|
W_A, not concurrent W and A in the same field). Each call should carry the complete current state.
Best practices: one-shot actions vs held actions
Use the 42 ms interval as the mental model:
One-shot action (for example, tap jump/attack or take one step): call once. The SDK sends it in the nearest interval; no repeated calls or follow-up None command is required.
None to reset the state. The SDK never generates a reset command automatically.
Signature
Parameters
Field | Type | Required | Description |
|---|---|---|---|
|
| Yes | A command object containing the three fields |
Returns
No return value (synchronous). State validation runs synchronously and immediately; the DataChannel write is throttled asynchronously.
Errors
code | Description |
|---|---|
| No active experience |
| State not allowed |
| Called outside adventure mode (directing / Acting) |
| Throttled flush send failure (non-fatal, asynchronous callback) |
HappyOyster.attachVideo
Returns a SurfaceView for playback, which you add to your layout; the SDK internally completes the render binding with the remote stream.
- Call on the main thread; an off-main call synchronously throws
IllegalStateException. - The SDK holds only a weak reference to the returned View and releases the render binding when the experience ends; you must remove the View from the layout yourself.
- For Acting experiences, size the playback container from
StartTravelData.aspectRatiobefore adding the returned view to your layout: rendering is bound with a clip-to-fill mode, so a mismatched orientation crops the picture (seestartTravel).
Signature
Parameters
No parameters.
Returns
Returns a SurfaceView; add it to your layout to play the real-time video.
Errors
code | Description |
|---|---|
| SDK not initialized |
HappyOyster.endTravel
Ends the experience. encryptedTravelId is obtained internally by the SDK. After a successful call (or an abnormal exit), the SDK automatically disconnects the real-time connection, stops internal polling, and releases all session resources, and the current ticket is invalidated at the same time.
Signature
Parameters
No parameters.
Returns
Returns EndTravelData (contains encryptedTravelId, status, endedAt, durationSec).
Errors
code | Description |
|---|---|
| SDK not initialized |
| No active experience |
HappyOyster.VERSION
Returns the SDK version string (SemVer), such as "x.y.z". This value is a compile-time constant injected by the VERSION_NAME Gradle property; it can be read safely without calling initialize first.
Signature
Returns
The SDK version string, such as "x.y.z".
Event Listening
Event | Description |
|---|---|
| Experience status change (including the real-time video lifecycle); values are defined in |
| Callback when an internal automatic flow fails; a fatal error also terminates the current experience (see the error-code section). |
addListener / removeListener must be called after initialize; calling them before initialization throws SDKError(100001). It is recommended to register listeners immediately after HappyOyster.initialize(...) returns successfully.Data Models
Error Codes
Errors are identified by a numeric code. The SDK passes through business error codes that the caller can handle (commonly 4xxxxx / 5xxxxx); local SDK error codes are 1xxxxx.
Business error codes (common)
code | Meaning | Suggested handling |
|---|---|---|
| Invalid parameter (invalid enum, etc.) | Check the request parameters or the SDK version |
|
| Have your server re-issue a credential |
|
| The credential is one-time; re-issue it |
| World does not exist, has been deleted, or does not belong to the current developer (including a world already deleted within the | Reselect a valid World |
| World not in a ready state | Wait until the world is ready before starting |
| Input content violation (content moderation); applies to | Modify the input content and retry |
| Service specification not enabled for this account | Do not retry as capacity-full; switch to an enabled spec or request enablement |
| Capacity configuration temporarily unavailable | Retry later |
| Travel resource does not exist or the ID does not belong to the current account | Restart the Travel |
| Request conflicts with the current resource state | Check the Travel state |
| SKU concurrency limit reached | Retry after an existing session ends (do not confuse with |
| No capacity currently available | Retry later |
| Inference resource allocation / internal service failure | Retry later |
| Internal system error | Retry later / report |
Client-side local error codes
code | Meaning | Fatal? | Suggested handling |
|---|---|---|---|
| Called before the SDK was initialized | Call rejected |
|
|
| Initialization failed | Pass a non-blank complete model name and version, then call |
| QwenCloud gateway API Key not injected | No | Retry after |
| QwenCloud gateway Bearer API Key expired or rejected. Note: this is the gateway Bearer Key, not the one-time ticket; ticket-level credential errors are identified by six-digit server codes (e.g., | No | Re-obtain the Bearer Key and |
| No active experience currently | Call rejected |
|
| The current state does not allow this operation (including: state mismatch; the experience does not support pause / resume / rewind for | Call rejected | Check the experience state and mode; |
| Mode mismatch: | Call rejected | Check whether the current mode matches the interface's requirement |
| A concurrent | Call rejected | Await |
| Real-time connection failed | Yes | End and restart |
| Real-time join timed out | Yes | End and restart |
| Timed out waiting for the video first frame | Yes | End and restart |
| Real-time channel not ready / send failed | No | Confirm the real-time channel is ready and retry |
| An SDK gateway HTTP signaling call did not return within | No | Retrying is recommended, or increase |
| Auto-end on no stream: the first frame never arrived, or the stream was interrupted during running and did not recover before timeout. The SDK proactively ends the current experience | Yes | End and restart |
| Local network error | No | Retryable |
| Response parsing failed | No | Thrown to the caller when triggered by an explicit call; on internal status polling, only |
| The upstream service returned an unrecognizable error response; the SDK has kept the original information in | No | Retryable; if it persists, investigate service availability together with |
| SDK remotely disabled (fully shut down or version too low); the reason is in | Yes (when the SDK detects a disable result, it automatically ends the in-progress experience; you can restart after recovery) | Prompt the user based on the reason in |
100001/100002/103xxx only reject/throw for that call (100002 directly fails initialization) and do not terminate an experience.- Fatal errors: the SDK automatically terminates the current experience (disconnects the real-time connection, releases resources, calls
endTravel) and surfaces it viaonError; the host should clean up the current experience state and allow a restart. There are four categories: ① internal status polling reads an experience status offailed(e.g.,500001inference failure); ② real-time connection fatal (105001/105002/105003); ③ auto-end on no stream (105006: the first frame never arrived, or the stream was interrupted during running and did not recover before timeout, and the SDK proactively ends it); ④ SDK remotely disabled (108001). - Non-fatal errors: do not terminate the experience and are only surfaced via
onError; you mayupdateTokenagain or wait for the service / real-time channel to recover before continuing. A single failed request of internal status polling itself (network106001, parsing106002, upstream anomaly106003, business error5xxxxx) falls into this category—polling continues in the next cycle, and only when it reads a status offaileddoes it end the experience; authentication failure101001and an asynchronoussendCommandthrottled-flush send failure105004are likewise non-fatal. Note: the SDK never sends any keepalive payload of its own over the real-time channel, so105004cannot appear while the session is idle—it can only be triggered by your ownsendCommandcall.