El punto de entrada para el SDK de Android de HappyOyster es el objeto singleton HappyOyster. Excepto initialize, updateToken, attachVideo y sendCommand, todos los métodos de negocio son funciones suspendidas que lanzan SDKError en caso de fallo. Cubre los tres modos aventura / dirección / actuación, la configuración de modelo requerida y aspectRatio.
Si la corrutina invocadora se cancela, el SDK cancela la solicitud HTTP en tránsito de esa llamada, si la hay, y propaga CancellationException sin cambios en lugar de convertirla a SDKError. La cancelación no revierte una solicitud que el servidor ya haya aceptado.
Para el flujo de integración, instalación y mejores prácticas, consulte Guía de integración del SDK de Happy Oyster para Android.
Terminología principal
Término | Significado |
|---|---|
token | API Key de la puerta de enlace de Bailian: inyectada como token Bearer por su aplicación mediante |
ticket | Credencial de experiencia de un solo uso: intercambiada por su servidor a través de la plataforma abierta y entregada al cliente, utilizada únicamente para una sola ejecución de |
Requisitos del entorno
Elemento | Requisito |
|---|---|
minSdk | 24 (Android 7.0) y superior |
compileSdk | 36 |
Lenguaje | Kotlin (APIs de corrutina |
ABI |
|
Red | Se requiere acceso a Internet público |
Conceptos principales
Concepto | Descripción |
|---|---|
Mundo | Un mundo de IA, que contiene personajes y escenas. Creado y gestionado por su servidor. |
Travel | Una única experiencia en tiempo real. Ciclo de vida básico: |
Modo |
|
Video en tiempo real | Establecida y mantenida automáticamente por el SDK después de que |
Visión general
Métodos de HappyOyster
Método | Descripción |
|---|---|
| Inicializa el SDK. Se admite la reinicialización en inactividad; la reinicialización mientras un Travel está iniciando, activo o finalizando se rechaza con |
| Inyecta/actualiza la clave de API de la puerta de enlace de Bailian (token Bearer). |
| Inicia una experiencia con una credencial de un solo uso y establece automáticamente la conexión de video en tiempo real. |
| Pausa la experiencia de forma asíncrona (dirección y Actuación, y solo si la experiencia admite la pausa); tras la aceptación, la pausa real se determina mediante |
| Reanuda una experiencia pausada (dirección y Actuación); incluye retroceso de reintento de 3× internamente. |
| Rebobina al número especificado de segundos (solo dirección, estado |
| Envía una instrucción de texto para impulsar la narrativa (dirección y Actuación, |
| Envía comandos de control de dirección/vista/acción (modo aventura, solo |
| Devuelve un |
| Finaliza la experiencia, desconectando automáticamente la conexión en tiempo real y liberando todos los recursos de la sesión. |
| Cadena de versión del SDK (SemVer), una constante en tiempo de compilación. |
Eventos
Evento | Descripción |
|---|---|
| Cambio de estado de la experiencia (incluido el ciclo de vida del video en tiempo real); los valores se definen en |
| Devolución de llamada cuando falla un flujo automático interno; un error fatal también termina la experiencia actual. |
Tipos de datos clave
Tipo | Descripción |
|---|---|
| Configuración de inicialización del SDK ( |
| Estado de la experiencia: |
| Modo de experiencia: |
| Modelo de creación de mundo: |
| Devuelto por |
| Parámetro para |
| Error del SDK; contiene |
HappyOyster.initialize
Inicializa el SDK; llámelo antes que cualquier otra API (preferiblemente en Application.onCreate). config es obligatorio y debe contener el apiHost de Bailian de su cuenta y un model de happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure (coincidiendo con la entrada de Open API). El SDK construye las URL de solicitud de la siguiente manera:
https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}
model no tiene valor predeterminado: omitirlo al construir SDKConfig genera un error en tiempo de compilación. Pasar un valor en blanco hace que initialize lance sincrónicamente SDKError(100002); no se crea ni reemplaza ningún entorno de ejecución y no se realiza ninguna solicitud de red. El SDK conserva únicamente el contexto de la aplicación, no una Activity. La reinicialización solo se admite mientras el entorno de ejecución anterior está inactivo. Para cambiar de modelo, reinicialice con el nuevo model mientras esté inactivo; si un Travel se está iniciando, está activo o está finalizando, espere primero a endTravel().
Nota
Aviso de cumplimiento regional:- Para respaldar el cumplimiento de los requisitos legales y de datos aplicables, si sus usuarios previstos incluyen usuarios de EE. UU., debe configurar un API Host de la región de EE. UU. durante la inicialización del SDK para los servicios proporcionados a dichos usuarios.
- Los desarrolladores son responsables de la configuración correcta y asumen la responsabilidad correspondiente bajo la ley aplicable por el incumplimiento de este requisito.
Firma
fun initialize(context: Context, config: SDKConfig)
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | Se recomienda pasar |
|
| Sí | Configuración del SDK; debe llevar los obligatorios y sin valor predeterminado |
Devuelve
Sin valor de retorno.
Errores
code | Descripción |
|---|---|
|
|
| Reinicialización rechazada porque un Travel está iniciando, activo o finalizando. Espere a |
HappyOyster.updateToken
Inyecta/actualiza la API Key de la puerta de enlace de Bailian. Es seguro para subprocesos y se puede llamar después de initialize; el SDK utiliza el token más reciente para startTravel y solicitudes posteriores de control de experiencia. El ticket es una credencial de experiencia de un solo uso y no debe confundirse con el token Bearer.
Firma
fun updateToken(token: String)
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | La clave de API de la puerta de enlace de Bailian, utilizada como token Bearer. |
Devuelve
Sin valor de retorno.
Errores
code | Descripción |
|---|---|
| El SDK no ha sido inicializado. |
HappyOyster.startTravel
Inicia una experiencia con un ticket de un solo uso. En caso de éxito, el SDK establece automáticamente la conexión de video en tiempo real e inicia el sondeo interno de estado, y el estado se expone mediante onStatusChanged.
ticketes una credencial de un solo uso y se considera consumida una vez realizada la llamada.- Solo se permite un Travel concurrente por aplicación en cualquier momento dado; llamar nuevamente mientras una experiencia está en curso lanza
SDKError(103004), conSDKError.rawconteniendo solo un resumen redactado del ticket actualmente activo para diagnóstico, nunca el ticket completo. - Opcionalmente pase
maxExperienceTimeSecpara limitar la duración máxima de esta experiencia (solo exploración de mundo / modo aventura); consulte Parámetros.
StartTravelData.creationModel (CreationModelValue, valor predeterminado CreationModelValue.Simple): indica el modelo de creación del mundo, es decir, cómo se gestiona el contenido del guion. simple (predeterminado) es un mundo ordinario basado en instrucciones; los mundos de exploración también se normalizan a este valor. scriptlist es un mundo estructurado ScriptList — no se accede a su contenido de guion a través de este SDK. En el modo ScriptList (creationModel == CreationModelValue.ScriptList), la llamada a sendInstruct se rechaza y lanza SDKError(103002). Este campo tiene como valor predeterminado simple.
StartTravelData.aspectRatio (String?): la relación de aspecto de reproducción que el servidor asignó a esta sesión, como una cadena width:height. No es null solo para Actuación—"9:16" (vertical, el valor predeterminado del servidor al crear) o "16:9" (horizontal); null para los modos de exploración de mundo y dirección, y null cuando el servidor no reporta el valor. El SDK conserva los valores desconocidos textualmente (no los reduce a null), por lo que los anfitriones deben tratar cualquier valor no reconocido como null y recurrir a su propia orientación predeterminada; el SDK en sí no consume este campo.
El valor se entrega con el valor de retorno de startTravel(): dimensione el contenedor de reproducción basándose en él después de que startTravel() retorne y antes de llamar a attachVideo() y añadir la vista devuelta a su diseño — el SDK solo comienza a vincular el flujo remoto para renderizado una vez que el anfitrión adjunta la vista, por lo que decidir la orientación en ese momento todavía precede al primer fotograma renderizado (sin embargo, no está garantizado que ocurra antes de que el SDK se una a la sala en tiempo real). La vista remota se vincula con un modo de renderizado recortar para llenar, por lo que un contenedor cuya orientación no coincida con este valor recortará la imagen en lugar de aplicar letterboxing.
Firma
suspend fun startTravel(ticket: String): StartTravelData
suspend fun startTravel(ticket: String, maxExperienceTimeSec: Int?): StartTravelData
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | La credencial de experiencia de un solo uso, proporcionada por su servidor. |
|
| No | Duración máxima de esta experiencia en segundos; el mundo finaliza la sesión automáticamente una vez alcanzada. Aplica únicamente al modo de exploración de mundo (aventura); se ignora en los modos de dirección (dirección en tiempo real) y Actuación. Los valores permitidos están configurados en el servidor, actualmente |
Devuelve
Devuelve StartTravelData, que contiene metadatos de la experiencia (mode, version, creationModel, aspectRatio, etc.). Inspeccione los metadatos devueltos antes de decidir cómo interactuar (p. ej., travel.mode, travel.creationModel, travel.version); para Actuación, también dimensione el contenedor de reproducción desde travel.aspectRatio.
Errores
code | Descripción |
|---|---|
|
|
|
|
| Parámetro inválido (p. ej., |
| El Mundo no está en estado listo |
| Asignación de recursos / fallo del servicio interno |
| Un Travel ya se está iniciando, está activo o finalizando, o |
HappyOyster.pauseTravel / HappyOyster.resumeTravel
Pausar / reanudar la experiencia. El SDK gestiona solo un Travel a la vez; encryptedTravelId se obtiene internamente por el SDK y no necesita ser pasado por el llamador.
Condiciones previas:
pauseTravel: llamable solo en dirección o Actuación y cuando el estado esrunning.resumeTravel: llamable solo en dirección o Actuación y cuando el estado espaused.
No todas las experiencias admiten pausa/reanudación: la experiencia debe reportar el identificador de versión que requiere su modo (StartTravelData.version—storyV2 para dirección, actingV2 para Actuación; comparado ignorando espacios en blanco circundantes y mayúsculas/minúsculas), de lo contrario tanto pauseTravel como resumeTravel devuelven 103002. La Actuación admite pausa/reanudación pero no admite rewindTravel.
La pausa es asíncrona (importante): pauseTravel es una operación "pesada" — una llamada exitosa (retorno del método) solo significa que la pausa ha sido aceptada; en ese momento la experiencia aún no está realmente pausada. La pausa solo es efectiva una vez que la devolución de llamada onStatusChanged reporta paused. Por lo tanto, controle la máquina de estados del anfitrión y la habilitación de llamadas desde esa devolución de llamada: entre "llamar a pauseTravel" y "recibir la devolución de llamada paused" puede marcar el estado local como pausing; solo después de recibir paused debe permitir iniciar resumeTravel o rewindTravel. No considere que el retorno de pauseTravel implica que ya está pausado.
Manejo de la conexión en tiempo real: después de una pausa real, el SDK desconecta la conexión en tiempo real; al reanudar, el SDK se vuelve a unir automáticamente y restaura la imagen utilizando la credencial entregada durante el mismo startTravel, sin requerir intervención del host.
Barrera ordenada de desmantelamiento por pausa: después de que se emite Paused, el desmantelamiento de la sala en tiempo real en el lado del servidor aún puede retrasarse brevemente. El SDK establece una ventana de estabilización de 3 segundos a partir de la confirmación de la pausa. Si se llama a resumeTravel o rewindTravel dentro de esa ventana, la llamada de suspensión espera primero de forma no bloqueante durante el tiempo restante y luego envía la solicitud de API para reapertura de la sala. No se añade ningún retraso si ya han transcurrido naturalmente 3 segundos. Esto evita que un desmantelamiento tardío por pausa cierre la sala recién abierta y genere 105001.
Reintento de API de reanudación: resumeTravel reintenta internamente hasta 3 veces en caso de fallo (retroceso de 1 s / 2 s / 3 s) para manejar indisponibilidad breve del servicio tras una pausa; solo si fallan los 3 intentos se propaga el error hacia arriba.
Nota⚠️ Nota: Se recomienda que el host espere hasta 3 s después de que resumeTravel retorne exitosamente antes de iniciar pauseTravel nuevamente, para evitar cambios demasiado frecuentes. Esta es una recomendación de enfriamiento de llamadas del lado del host y no es impuesta por el propio SDK. Consulte la Guía de integración del SDK de Android de Happy Oyster para más detalles.
Firma
suspend fun pauseTravel(): TravelStateData
suspend fun resumeTravel(): TravelStateData
Parámetros
Sin parámetros.
Devuelve
Devuelve TravelStateData (contiene encryptedTravelId, status).
Errores
code | Descripción |
|---|---|
| Sin experiencia activa |
| Estado no permitido, o esta experiencia no admite pausa / reanudación |
| Llamado en modo aventura; solo dirección y Actuación admiten pausa / reanudación |
HappyOyster.rewindTravel
Rebobina al número especificado de segundos. encryptedTravelId se obtiene internamente por el SDK.
Precondiciones: solo se puede invocar en modo de dirección y cuando el estado es paused (no se permite rebobinar en el estado running). No todas las experiencias de dirección admiten el rebobinado, y las que no lo admiten devuelven 103002. Después de un rebobinado exitoso, la experiencia se reanuda automáticamente y el SDK reconecta RTC de forma automática, sin necesidad de intervención por parte del anfitrión.
La Actuación no tiene capacidad de rebobinado en absoluto: llamarla en Actuación es rechazado localmente por el SDK con 103003, y no se emite ninguna solicitud. En Actuación, los hosts deben ocultar el punto de entrada de rebobinado en lugar de simplemente deshabilitar el botón.
Firma
suspend fun rewindTravel(rewindToSec: Double): RewindTravelData
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | El número objetivo de segundos al que rebobinar, que debe ser un múltiplo de 4 (p. ej., 4, 8, 12). Un valor no múltiplo de 4 es redondeado hacia abajo por el servidor al múltiplo menor más cercano de 4 (p. ej., pasar 7 produce 4). |
Devuelve
Devuelve RewindTravelData (contiene encryptedTravelId, status, resumedAtSec), donde resumedAtSec es el número de segundos al que el servidor realmente rebobinó (ya redondeado hacia abajo al múltiplo más cercano de 4).
Errores
code | Descripción |
|---|---|
| Sin experiencia activa |
| El estado no es |
| Llamado en modo aventura o Actuación; solo el modo dirección admite rebobinado |
HappyOyster.sendInstruct
Envía una instrucción de texto para impulsar la narrativa. Válido en dirección o Actuación y cuando el estado de la experiencia es running o paused. encryptedTravelId se obtiene internamente por el SDK.
Comportamiento en estado pausado: el SDK no reanuda automáticamente mientras está pausado; la instrucción se envía directamente. Corresponde al host decidir si llamar primero a resumeTravel antes de enviar la instrucción.
Firma
suspend fun sendInstruct(content: String): SendInstructData
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | El contenido de la instrucción de texto a enviar. |
Devuelve
Devuelve SendInstructData (contiene encryptedTravelId, content, accepted).
Errores
code | Descripción |
|---|---|
| Sin experiencia activa (nunca se llamó a |
| El modo actual no es ni dirección ni Actuación (llamado en modo aventura) |
| El estado de la experiencia no es ni |
| Bloqueo por moderación de contenido |
| El Travel no existe |
HappyOyster.sendCommand
Envía comandos de control de dirección/vista/acción. Válido solo en modo aventura y cuando running.
- Llame en el hilo principal; una llamada fuera del hilo principal lanza sincrónicamente
IllegalStateException. - Si no hay experiencia activa, se reporta
103001. - Llamar fuera del modo aventura (dirección / Actuación) reporta
103003. - Un estado no permitido reporta
103002. - El enlace ascendente se establece mediante un flujo de audio silencioso (el SDK no graba ni carga audio real);
RECORD_AUDIOno es un requisito estricto para el DataChannel, pero declararlo y otorgarlo es recomendable para garantizar la compatibilidad entre dispositivos (consulte Guía de integración del SDK de Android de Happy Oyster § Instalación · Permisos).105004significa que el canal en tiempo real no está listo o que falló un envío (por ejemplo, una interrupción de la conexión del DataChannel o una anomalía en el canal en tiempo real); no se activa directamente por la falta de un permiso.
Limitación de frecuencia integrada de 42 ms (24 fps, prevalece el último): sendCommand limita internamente las escrituras en el DataChannel en intervalos de 42 ms (aproximadamente 24 fps). La validación de estado se ejecuta sincrónica e inmediatamente, lanzando un error al instante ante una llamada ilegal; la escritura real en el DataChannel se limita de forma asíncrona. Si han transcurrido al menos 42 ms desde el envío anterior, el comando se despacha inmediatamente (incluida la primera llamada). De lo contrario, el comando pendiente se reemplaza con el valor más reciente y se despacha una sola vez al final de la ventana actual de 42 ms. Por lo tanto, múltiples llamadas en una misma ventana producen una única escritura en la red que contiene el último valor. El anfitrión puede llamar a sendCommand a la tasa de fotogramas del juego sin implementar su propio limitador de frecuencia.
Fallos de envío durante la limitación: el vaciado limitado es asíncrono, y un fallo de envío no se puede lanzar al invocador; el error se expone mediante el callback onError (no fatal, 105004). Los comandos pendientes se descartan cuando finaliza la sesión (no se enviarán después de que termine la sesión).
Campos y valores de AdventureCommand:
Campo | Semántica | Valores |
|---|---|---|
| Movimiento: adelante/izquierda/atrás/derecha/diagonal/inactivo |
|
| Vista: arriba/abajo/izquierda/derecha/diagonal/none |
|
| Interacción: saltar/atacar/agacharse/correr/none |
|
Los tres campos son grupos de comandos mutuamente excluyentes e independientes. El movimiento/vista diagonal utiliza un único valor combinado (por ejemplo, adelante+izquierda es W_A, no W y A concurrentes en el mismo campo). Cada llamada debe llevar el estado actual completo.
Utilice el intervalo de 42 ms como modelo mental:
-
Acción instantánea (por ejemplo, tocar saltar/atacar o dar un paso): llamar una vez. El SDK la envía en el intervalo más cercano; no se requieren llamadas repetidas ni un comando de seguimiento
None.// Jump once HappyOyster.sendCommand(AdventureCommand("None", "None", "Jump")) -
Acción mantenida (por ejemplo, seguir moviéndose o rotando): llamar cada fotograma mientras se mantiene. El SDK emite aproximadamente un comando cada 42 ms. Al soltar, envíe explícitamente un comando que contenga
Nonepara restablecer el estado. El SDK nunca genera un comando de restablecimiento automáticamente.// 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"))
Firma
fun sendCommand(command: AdventureCommand)
Parámetros
Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
|
| Sí | Un objeto de comando que contiene los tres campos |
Devuelve
Sin valor de retorno (sincrónico). La validación de estado se ejecuta sincrónica e inmediatamente; la escritura en DataChannel se limita asincrónicamente.
Errores
code | Descripción |
|---|---|
| Sin experiencia activa |
| Estado no permitido |
| Llamado fuera del modo aventura (dirección / Actuación) |
| Fallo de envío de vaciado limitado (no fatal, devolución de llamada asíncrona) |
HappyOyster.attachVideo
Devuelve un SurfaceView para reproducción, que usted agrega a su diseño; el SDK completa internamente el enlace de renderizado con el flujo remoto.
- Llame en el hilo principal; una llamada fuera del hilo principal lanza sincrónicamente
IllegalStateException. - El SDK mantiene solo una referencia débil a la Vista devuelta y libera el enlace de renderizado cuando finaliza la experiencia; debe eliminar la Vista del diseño usted mismo.
- Para experiencias de Actuación, dimensione el contenedor de reproducción desde
StartTravelData.aspectRatioantes de agregar la vista devuelta a su diseño: la renderización está vinculada con un modo clip-to-fill, por lo que una orientación no coincidente recorta la imagen (verstartTravel).
Firma
fun attachVideo(): SurfaceView
Parámetros
Sin parámetros.
Devuelve
Devuelve un SurfaceView; agréguelo a su diseño para reproducir el video en tiempo real.
Errores
code | Descripción |
|---|---|
| SDK no inicializado |
HappyOyster.endTravel
Finaliza la experiencia. encryptedTravelId se obtiene internamente por el SDK. Después de una llamada exitosa (o una salida anormal), el SDK desconecta automáticamente la conexión en tiempo real, detiene el sondeo interno y libera todos los recursos de la sesión, y el ticket actual se invalida al mismo tiempo.
Firma
suspend fun endTravel(): EndTravelData
Parámetros
Sin parámetros.
Devuelve
Devuelve EndTravelData (contiene encryptedTravelId, status, endedAt, durationSec).
Errores
code | Descripción |
|---|---|
| SDK no inicializado |
| Sin experiencia activa |
HappyOyster.VERSION
Devuelve la cadena de versión del SDK (SemVer), como "x.y.z". Este valor es una constante de tiempo de compilación inyectada por la propiedad Gradle VERSION_NAME; se puede leer de forma segura sin necesidad de llamar primero a initialize.
Firma
val VERSION: String
Devuelve
La cadena de versión del SDK, como "x.y.z".
Log.d("MyApp", "SDK version: ${HappyOyster.VERSION}")
Escucha de eventos
interface HappyOysterListener {
fun onStatusChanged(status: TravelStatusValue) {}
fun onError(error: SDKError) {}
}
fun addListener(listener: HappyOysterListener)
fun removeListener(listener: HappyOysterListener)
Los eventos del SDK son un canal de envío proactivo hacia usted, utilizado para informar situaciones no desencadenadas por sus propias llamadas explícitas (p. ej., problemas en la conexión en tiempo real o en el sondeo de estado que el SDK mantiene automáticamente).
Evento | Descripción |
|---|---|
| Cambio de estado de la experiencia (incluido el ciclo de vida del video en tiempo real); los valores se definen en |
| Devolución de llamada cuando falla un flujo automático interno; un error fatal también termina la experiencia actual (consulte la sección de códigos de error). |
Nota⚠️ Nota: addListener / removeListener deben llamarse después de initialize; llamarlos antes de la inicialización lanza SDKError(100001). Se recomienda registrar los listeners inmediatamente después de que HappyOyster.initialize(...) retorne exitosamente.
Modelos de datos
// 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")
Códigos de error
Los errores se identifican mediante un code numérico. El SDK transmite los códigos de error de negocio que el invocador puede manejar (comúnmente 4xxxxx / 5xxxxx); los códigos de error locales del SDK son 1xxxxx.
Códigos de error de negocio (comunes)
code | Significado | Manejo sugerido |
|---|---|---|
| Parámetro no válido (enum no válido, etc.) | Verifique los parámetros de la solicitud o la versión del SDK |
|
| Haga que su servidor vuelva a emitir una credencial |
|
| La credencial es de un solo uso; vuélvala a emitir |
| El mundo no existe, ha sido eliminado o no pertenece al desarrollador actual (incluido un mundo ya eliminado dentro de la credencial de | Vuelva a seleccionar un Mundo válido |
| El Mundo no está en estado listo | Espere hasta que el mundo esté listo antes de comenzar |
| Violación de contenido de entrada (moderación de contenido); aplica a instrucciones de texto de | Modifique el contenido de entrada e inténtelo de nuevo |
| Especificación de servicio no habilitada para esta cuenta | No reintente como capacidad llena; cambie a una especificación habilitada o solicite la habilitación |
| Configuración de capacidad temporalmente no disponible | Reintentar más tarde |
| El recurso Travel no existe o el ID no pertenece a la cuenta actual | Reiniciar el Travel |
| La solicitud entra en conflicto con el estado actual del recurso | Verificar el estado del Travel |
| Límite de concurrencia de SKU alcanzado | Reintente después de que finalice una sesión existente (no confundir con |
| No hay capacidad disponible actualmente | Reintentar más tarde |
| Asignación de recursos de inferencia / fallo del servicio interno | Reintentar más tarde |
| Error interno del sistema | Reintentar más tarde / reportar |
Códigos de error locales del lado del cliente
code | Significado | ¿Fatal? | Manejo sugerido |
|---|---|---|---|
| Llamado antes de que el SDK fuera inicializado | Llamada rechazada |
|
|
| Error de inicialización | Proporcione un nombre de modelo y versión completos y no vacíos, luego llame a |
| Clave API de la puerta de enlace de Bailian no inyectada | No | Reintentar después de |
| La API Key Bearer de la puerta de enlace de Bailian caducó o fue rechazada. Nota: esta es la Bearer Key de la puerta de enlace, no el ticket de un solo uso; los errores de credenciales a nivel de ticket se identifican mediante códigos de servidor de seis dígitos (p. ej., | No | Obtenga nuevamente la clave Bearer y |
| No hay experiencia activa actualmente | Llamada rechazada |
|
| El estado actual no permite esta operación (incluye: discrepancia de estado; la experiencia no admite pausa / reanudación / rebobinado para | Llamada rechazada | Verifique el estado y el modo de la experiencia; |
| Desajuste de modo: | Llamada rechazada | Compruebe si el modo actual coincide con el requisito de la interfaz |
| Una llamada concurrente a | Llamada rechazada | Espere a |
| Error de conexión en tiempo real | Sí | Finalizar y reiniciar |
| Tiempo de espera agotado para la unión en tiempo real | Sí | Finalizar y reiniciar |
| Tiempo de espera agotado esperando el primer cuadro de video | Sí | Finalizar y reiniciar |
| Canal en tiempo real no listo / error de envío | No | Confirme que el canal en tiempo real esté listo y reintente |
| Una llamada de señalización HTTP de la puerta de enlace del SDK no respondió dentro de | No | Se recomienda reintentar o aumentar |
| Finalización automática por ausencia de flujo: el primer fotograma nunca llegó, o el flujo se interrumpió durante la ejecución y no se recuperó antes del tiempo de espera. El SDK finaliza proactivamente la experiencia actual | Sí | Finalizar y reiniciar |
| Error de red local | No | Reintentable |
| Error al analizar la respuesta | No | Lanzado al llamador cuando se activa mediante una llamada explícita; en el sondeo de estado interno, solo se emite |
| El servicio ascendente devolvió una respuesta de error irreconocible; el SDK ha conservado la información original en | No | Reintentable; si persiste, investigue la disponibilidad del servicio junto con |
| SDK deshabilitado remotamente (apagado total o versión demasiado baja); el motivo está en | Sí (cuando el SDK detecta un resultado de deshabilitación, finaliza automáticamente la experiencia en curso; puede reiniciar después de la recuperación) | Informe al usuario basándose en el motivo en |
Nota"¿Fatal?" se refiere específicamente a si desencadena que el SDK finalice automáticamente la experiencia actual. Los errores de validación síncrona como 100001/100002/103xxx solo rechazan/lanzan para esa llamada (100002 falla directamente la inicialización) y no finalizan una experiencia.
Errores fatales vs no fatales:
- Errores fatales: el SDK termina automáticamente la experiencia actual (desconecta la conexión en tiempo real, libera recursos, llama a
endTravel) y lo notifica medianteonError; el anfitrión debe limpiar el estado de la experiencia actual y permitir un reinicio. Existen cuatro categorías: ① el sondeo interno de estado lee un estado de experiencia defailed(por ejemplo, fallo de inferencia500001); ② error fatal de conexión en tiempo real (105001/105002/105003); ③ finalización automática por ausencia de flujo (105006: el primer fotograma nunca llegó, o el flujo se interrumpió durante la ejecución y no se recuperó antes del tiempo de espera, y el SDK lo finaliza proactivamente); ④ SDK deshabilitado remotamente (108001). - Errores no fatales: no terminan la experiencia y solo se notifican mediante
onError; puede llamar aupdateTokennuevamente o esperar a que el servicio / canal en tiempo real se recupere antes de continuar. Un único fallo en una solicitud del propio sondeo interno de estado (error de red106001, error de análisis106002, anomalía ascendente106003, error de negocio5xxxxx) entra en esta categoría — el sondeo continúa en el siguiente ciclo, y solo cuando lee un estado defailedfinaliza la experiencia; el fallo de autenticación101001y un fallo de envío por descarga limitada asíncrona desendCommand105004tampoco son fatales. Nota: el SDK nunca envía ninguna carga útil de keepalive propia a través del canal en tiempo real, por lo que105004no puede aparecer mientras la sesión está inactiva — solo puede ser activado por su propia llamada asendCommand.