Todos los productos
Search
Centro de documentos

Alibaba Cloud Model Studio:Guía de integración del SDK de HappyOyster para Android

Última actualización: Oct 12, 2026

Al integrar el SDK de HappyOyster para Android, su aplicación puede ingresar a mundos generados por IA en tiempo real, ofreciendo una experiencia de video interactiva en tiempo real en tres modos: Adventure (exploración de mundos), Directing (dirección en tiempo real) y Acting (juego de roles).

Happy Oyster es un producto de exploración de mundos con IA. Al integrar el SDK de Android Happy Oyster, su aplicación puede entrar en "mundos" generados por IA en tiempo real, ofreciendo una experiencia de video interactiva en tiempo real en aventura, dirección o actuación.

Este documento está dirigido a desarrolladores de Android en el lado de la integración. Cubre instalación, autenticación, el flujo completo de integración, manejo de eventos y mejores prácticas, explicando cómo integrar de manera confiable. Para APIs específicas (firmas, parámetros, valores de retorno, códigos de error, modelos de datos), consulte la Referencia de API del SDK de Android Happy Oyster.

1. Qué puede hacer

  • Iniciar una experiencia (Travel): Entre a un mundo listo con una credencial de un solo uso; el SDK establece automáticamente la conexión de video en tiempo real.
  • Reproducción en tiempo real: El SDK devuelve una View de video que usted monta en su diseño para reproducir los visuales generados en tiempo real por la IA.
  • Interacción en tiempo real:
    • modo dirección (directing): Envíe instrucciones de texto para impulsar la trama.
    • actuación: También envíe instrucciones de texto; pausar/reanudar están disponibles; no rebobine y no utilice sendCommand. Utilice el aspectRatio de la respuesta de unión para establecer la orientación del reproductor (consulte §13 Adaptación de modos).
    • modo aventura (adventure): Envíe comandos de control de dirección/punto de vista/acción para interactuar con el mundo.
  • Control de proceso: Pausar / reanudar (dirección y actuación), rebobinar (solo dirección), finalizar (los tres modos).
  • Callbacks de estado y error: Perciba el estado de la experiencia y las excepciones en tiempo real a través de listeners de eventos.

NotaEl SDK no es responsable de la creación y gestión de mundos, ni expone directamente detalles de comunicación en tiempo real de bajo nivel; estos son manejados por su servidor o internamente por el SDK. Solo necesita enfocarse en "iniciar experiencia → reproducir → interactuar → finalizar".

2. Instalación

El SDK Happy Oyster (cn.happyoyster:opensdk) se publica en Maven Central; su motor de comunicación en tiempo real subyacente (Alibaba Cloud ARTC) se publica en Alibaba Cloud Maven. Ambos repositorios deben declararse.

Requisitos del entorno

Elemento

Requisito

minSdk

24 (Android 7.0) y superior

compileSdk

36

JDK

Objetivo de bytecode JDK 11 (cadena de herramientas del host: se recomienda JDK 11 o superior)

Lenguaje

Kotlin (API de corutina suspend)

ABI

arm64-v8a / armeabi-v7a (el motor de comunicación en tiempo real contiene bibliotecas nativas)

Red

Se requiere acceso a Internet público

Agregue los repositorios en el settings.gradle.kts de la raíz del proyecto:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()                                       // Happy Oyster SDK (cn.happyoyster:opensdk)
        maven("https://maven.aliyun.com/repository/public")  // real-time communication engine (Alibaba Cloud ARTC)
    }
}

Encuentre la última versión publicada en Maven Central, reemplace <version> a continuación con ese número de versión exacto y agregue la dependencia en el build.gradle.kts del módulo:

dependencies {
    implementation("cn.happyoyster:opensdk:<version>")
}

Fije la dependencia a una versión exacta. Revise las notas de la versión antes de actualizar y recompile el código del host después de la actualización.

NotaEl SDK se publica como un AAR ligero y no integra dependencias de terceros; las dependencias transitivas, como el motor de comunicación en tiempo real, se obtienen automáticamente de los repositorios anteriores durante la resolución, por lo que el repositorio Alibaba Cloud Maven es indispensable; su ausencia provocará que com.aliyun.aio:AliVCSDK_ARTC falle en la resolución.

Permisos

La biblioteca del SDK en sí declara únicamente INTERNET. El motor de comunicación en tiempo real combina automáticamente un pequeño número de permisos de tipo red/Bluetooth/configuración de audio; el manifiesto de la biblioteca no incluye permisos de micrófono o cámara. La transmisión de video es reproducción de solo suscripción y el SDK no envía audio real al lado remoto.

El host debe declarar por sí mismo: Cuando su targetSdk es 33 o superior, debe declarar el permiso de notificación en el AndroidManifest.xml del host (el motor de comunicación en tiempo real incluye un servicio en primer plano):

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

modo aventura (sendCommand) — se recomienda declarar el permiso de micrófono: Los comandos de control en tiempo real del modo aventura viajan en un enlace ascendente transportado por un flujo de audio silencioso que establece la "identidad de publicador" local; el SDK no graba ni sube su audio real. RECORD_AUDIO no es un prerequisito estricto para el DataChannel—incluso sin él, el flujo silencioso sigue actuando como publicador y el enlace ascendente normalmente está disponible. Sin embargo, para garantizar estabilidad entre dispositivos, recomendamos que, si su aplicación utiliza sendCommand, declare el permiso en el AndroidManifest.xml del host y lo solicite en tiempo de ejecución antes de llamar.

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Acerca del código de error 105004: 105004 significa que el canal en tiempo real no está listo o que falló un envío (por ejemplo, una interrupción de DataChannel o una anomalía del canal en tiempo real). No es una consecuencia necesaria de un permiso faltante—no se activa directamente porque RECORD_AUDIO no haya sido concedido. El modo de dirección (sendInstruct) y la reproducción de video no se ven afectados. Si su aplicación no utiliza el modo aventura, este permiso no es necesario.

Para eliminar los permisos combinados, utilice tools:node="remove".

3. Modelo de autenticación

El SDK no obtiene ni actualiza tokens, manteniéndose ligero. La autenticación tiene dos capas:

  1. API Key de la puerta de enlace de Bailian: Su aplicación la inyecta como un token Bearer mediante updateToken(token). El SDK conserva únicamente la más reciente; no la persiste ni la actualiza automáticamente, por lo que tras un cambio de Key debe volver a inyectarla. La puerta de enlace exige que todas las solicitudes del SDK, incluida startTravel, utilicen este Bearer. Cuando el Bearer Key caduca o no es válido, el SDK lanza SDKError(101002); en este caso, debe obtener e inyectar nuevamente el Bearer Key (mediante updateToken) en lugar de canjear un nuevo ticket. Durante el desarrollo o pruebas de Demo, puede usar directamente su API Key principal de Bailian como Bearer (mediante updateToken) para validar el flujo. En producción, cambie siempre a un token de corta duración generado por su servidor, y nunca incluya una API Key de larga duración en una aplicación distribuida.
  2. Credencial de experiencia de un solo uso ticket: Intercambiada por su servidor a través de la plataforma abierta y entregada al cliente; se utiliza únicamente para un solo startTravel. Se vuelve inválida después de que finaliza la experiencia (normal o anormalmente) y no puede reutilizarse. Los errores de credenciales a nivel de ticket se identifican mediante códigos de servidor de seis dígitos (por ejemplo, 401010 = ticket inválido/vencido, 401011 = ticket ya utilizado), los cuales el SDK transmite textualmente.

Su servidor es responsable de crear y gestionar el Mundo, canjear el ticket de Travel y entregarlo al cliente; el SDK de Android solo consume tokens y tickets, y no proporciona ninguna interfaz de gestión del Mundo. Durante la inicialización, debe pasar el API Host de su cuenta de Bailian mediante SDKConfig.apiHost (con el formato llm-xxxx.ap-southeast-1.maas.aliyuncs.com, copiado del campo "API Host" en la página de API Key de la consola de Bailian) y proporcionar el valor requerido y sin valor predeterminado de SDKConfig.model (happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure, que coincide con la entrada de Open API). El SDK completa la URL de solicitud como https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}, por lo que usted no necesita ensamblarla manualmente.

Omitir model al construir SDKConfig genera un error en tiempo de compilación. Si model está en blanco, initialize lanza sincrónicamente SDKError(100002) (raw = "SDKConfig.model must not be blank"), no crea ni reemplaza ningún entorno de ejecución y no realiza ninguna solicitud de red. El API Host, el modelo y la API Key inyectada deben coincidir con la cuenta, la región y la autorización de modelo requeridas; de lo contrario, la puerta de enlace generalmente devuelve AccessDenied (en tiempo de ejecución esto se lanza como SDKError(106003), con el cuerpo original de AccessDenied disponible en SDKError.raw; consulte la fila 106003 de la tabla de códigos de error de la Referencia de API para obtener una lista de verificación de solución de problemas). Por motivos de seguridad, recomendamos encarecidamente que el cliente inyecte un token de corta duración generado por su servidor como Bearer, en lugar de incluir una API Key de larga duración en la aplicación o confirmarla en un repositorio de código.

NotaAviso de cumplimiento regional: Para facilitar 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 según la legislación aplicable en caso de incumplimiento de este requisito.

4. Inicio rápido

import cn.happyoyster.opensdk.*   // All entry-point classes live in this package: HappyOyster, SDKConfig, TravelStatusValue, ModeValue, SDKError, etc.

// Track the current experience status and this Travel's metadata; use them to decide whether interaction can be sent.
@Volatile private var currentStatus: TravelStatusValue? = null
@Volatile private var currentTravel: StartTravelData? = null

// 1) Initialize (recommended in Application.onCreate)
//    apiHost is required: your account's Bailian API Host (copied from the API Key page in the Bailian console)
//    model is required with no default: the complete model name and version; refer to the official Bailian HappyOyster model documentation for available values
HappyOyster.initialize(
    applicationContext,
    SDKConfig(
        apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com",
        model = "happyoyster-1.0",
    ),
)

// 2) Inject the Bailian gateway API Key (as the Bearer token)
HappyOyster.updateToken(bailianApiKey)

// 3) Listen to SDK events: use onStatusChanged to drive the host state machine and gate interaction capabilities
HappyOyster.addListener(object : HappyOysterListener {
    override fun onStatusChanged(status: TravelStatusValue) {
        currentStatus = status
        when (status) {
            // Interaction is allowed only in running; adventure-mode sendCommand is valid only in running.
            TravelStatusValue.Running -> markInteractionAllowed()
            // Paused is the signal that pauseTravel has actually taken effect (asynchronous, see below); sendInstruct is still allowed here.
            TravelStatusValue.Paused -> markTravelPaused()
            // Terminal states: the SDK has already ended and released resources; clean up the host-side experience state.
            TravelStatusValue.Completed, TravelStatusValue.Failed -> clearActiveTravel()
            // init / pending: not yet ready, interaction unavailable.
            else -> markInteractionBlocked()
        }
    }
    override fun onError(error: SDKError) {
        // Unified error callback; see the error-codes section of the API Reference
    }
})

// 4) Start the experience (the ticket is delivered by your server)
lifecycleScope.launch {
    try {
        // Only one concurrent Travel is allowed per App at a time (alirtc limitation: even with a different ticket
        // you cannot start a second Travel concurrently within the same App). Calling again while an experience is in progress throws
        // SDKError(103004), whose raw carries only a redacted summary of the ticket currently playing, never the full ticket.
        val travel: StartTravelData = HappyOyster.startTravel(ticket)
        currentTravel = travel

        // Inspect the returned Travel metadata first, then decide how to interact:
        //   travel.mode          —— directing / adventure / acting
        //   travel.creationModel —— Simple / ScriptList
        //   travel.aspectRatio   —— Acting only; use to set player orientation
        val canSendInstruct = (travel.mode == ModeValue.Directing || travel.mode == ModeValue.Acting) &&
            travel.creationModel != CreationModelValue.ScriptList
        // Note: calling sendInstruct in ScriptList mode throws SDKError(103002); script content is not managed through this SDK.

        // 5) Mount the video View (the SDK returns the view; you add it to the layout)
        //    Acting: size the container from travel.aspectRatio first, then attachVideo() (see §13 Mode Adaptation) —
        //    the remote view is bound clip-to-fill, so a mismatched container orientation crops the picture.
        val videoView = HappyOyster.attachVideo()
        binding.videoContainer.addView(videoView)

        // 6) In-run interaction — you must wait for running before sending:
        //    - sendCommand (adventure): valid only in running; calling in init/pending/paused returns 103002.
        //    - sendInstruct (directing / Acting): valid in running or paused; calling in init/pending returns 103002.
        if (canSendInstruct && currentStatus == TravelStatusValue.Running) {
            HappyOyster.sendInstruct("突然下起了大雨")
        }
    } catch (e: SDKError) {
        // Handle start failure (e.g., 103004: an experience is already playing)
    }
}

// 7) Pause / Resume (directing and Acting; rewind is directing only)
//    pauseTravel is asynchronous: the method's return only means "accepted"; the actual pause is determined by onStatusChanged(paused).
//    Be sure to wait for the paused callback before allowing resumeTravel.
lifecycleScope.launch {
    if ((currentTravel?.mode == ModeValue.Directing || currentTravel?.mode == ModeValue.Acting) &&
        currentStatus == TravelStatusValue.Running
    ) {
        HappyOyster.pauseTravel()        // accepted; the host marks pausing and waits for the status callback
        // …after receiving onStatusChanged(Paused)…
    }
}
lifecycleScope.launch {
    if (currentStatus == TravelStatusValue.Paused) {
        HappyOyster.resumeTravel()       // call only after paused
    }
}

// 8) End the experience (the SDK automatically disconnects the real-time connection and releases resources)
lifecycleScope.launch { HappyOyster.endTravel() }

5. Suscripción a eventos y manejo de errores

Utilice onStatusChanged para impulsar la máquina de estados del host y controlar las capacidades de interacción; utilice onError para recibir errores de ejecución de manera uniforme. Para la interfaz de eventos y los códigos de error completos, consulte la Referencia de API del SDK de Android Happy Oyster.

Obligatorio
  • Registre el listener inmediatamente después de que HappyOyster.initialize(...) regrese exitosamente; addListener / removeListener deben llamarse después de initialize (llamarlos antes de la inicialización lanza SDKError(100001)).
  • Condicioné a onStatusChanged(Running): las llamadas de interacción solo están permitidas después de running; el sendCommand del modo aventura solo es válido en running.
  • Maneje también onError; no capture únicamente startTravel. Los errores fatales también terminan la experiencia (consulte la sección de códigos de error de la Referencia de API).
Recomendado
  • Llame a removeListener en un punto apropiado del ciclo de vida (como onDestroy) para evitar fugas de memoria.
Evitar
  • Registrar listeners repetidamente sin llamar a removeListener.

6. Envío de instrucciones: sendInstruct y sendCommand

sendInstruct (dirección / actuación)

sendInstruct se utiliza en dirección y actuación para enviar instrucciones de texto que impulsan la imagen; se puede llamar tanto en el estado running como en el paused. En el estado de pausa, el SDK no reanuda automáticamente; el host decide si llamar primero a resumeTravel y luego enviar la instrucción (para detalles del contrato como limitación de tasa y validación de estado, consulte la Referencia de la API). El creationModel de un mundo de actuación es siempre Simple, por lo que la restricción ScriptList se aplica solo a la dirección.

sendCommand (modo aventura)

sendCommand se utiliza en modo aventura (adventure) para enviar comandos de control de dirección/punto de vista/acción; solo es válido en running. El SDK tiene un limitador incorporado de "el último gana" de 42 ms (24 fps), por lo que el host puede llamarlo a la tasa de fotogramas del juego y el SDK fusiona automáticamente; no se requiere limitación manual de tasa. Llame una vez para una acción instantánea. Para una acción mantenida, siga llamando en cada fotograma mientras se mantiene y envíe explícitamente un restablecimiento None al soltar (para el contrato detallado de limitación de tasa y exposición de fallos, consulte la Referencia de la API).

Práctica recomendada: En la interfaz de usuario de interacción del modo aventura del host, proporcione tres puntos de entrada de comandos independientes (dirección de movimiento + dirección de vista + interacción de acción), mantenga el valor retenido actualmente para cada grupo y envíe una instantánea completa de tres campos en cada llamada. Utilice "None" solo para dimensiones inactivas. Cuando se retienen entradas de diferentes dimensiones simultáneamente, conserve y envíe todos los valores activos en lugar de restablecer las otras dimensiones a "None".

7. Pausar / Reanudar / Rebobinar

Modos aplicables: pausa/reanudación se aplican a dirección y actuación, y se comportan de manera idéntica en ambos (incluida la barrera de 3 segundos a continuación); rebobinar es solo para dirección. Llamar a pauseTravel / resumeTravel / rewindTravel en modo aventura es rechazado por el SDK con 103003. Además, la version reportada por el servidor debe ser el token v2 que requiere su modo (storyV2 para dirección, actingV2 para actuación); de lo contrario, pausa/reanudación devuelven 103002.

La pausa es asíncrona: El retorno del método pauseTravel solo indica que ha sido aceptado; la pausa real está determinada por onStatusChanged(Paused). Entre "llamar a pauseTravel" y "recibir la devolución de llamada Paused", el host puede marcar su estado de experiencia local como pausing; solo después de recibir Paused debe permitir llamar a resumeTravel o al dependiente del estado de pausa rewindTravel.

Barrera interna del SDK pausa→reapertura: una vez que se recibe Paused, el host puede llamar a resumeTravel / rewindTravel según el contrato. Si la llamada ocurre dentro de la ventana de estabilización de 3 segundos tras la confirmación de la pausa, el método de suspensión del SDK espera el tiempo restante antes de enviar la solicitud que reabre la sala en tiempo real. El host no necesita un retraso adicional de pausa→reanudación, y no se añade ningún retraso una vez que han transcurrido naturalmente 3 segundos.

Tiempo de espera entre llamadas del lado del host (recomendado): Se recomienda que el host espere antes de emitir otro pauseTravel dentro de 3 s después de que resumeTravel regrese exitosamente, para evitar cambios demasiado frecuentes. El SDK en sí no impone este tiempo de espera.

Rebobinar: rewindTravel está disponible solo en el estado paused; después de rebobinar, el servidor reanuda automáticamente y el SDK reconecta automáticamente RTC, sin necesidad de intervención del host. Secuencia típica: pause → wait:paused → rewindTravel(sec). Los segundos de rebobinado rewindToSec deben ser múltiplos de 4 (por ejemplo, 4, 8, 12); los valores no múltiplos son redondeados hacia abajo por el servidor (por ejemplo, 7→4), y los segundos efectivos reales están determinados por el resumedAtSec devuelto. Rebobinar es solo para dirección: llamar a rewindTravel en modo actuación o aventura es rechazado por el SDK localmente con 103003, sin emitir ninguna solicitud HTTP; el host debe ocultar la entrada de rebobinado en lugar de simplemente atenuar el botón.

Para detalles del contrato, como semántica asíncrona y retroceso de reintento 3× (retroceso de resumeTravel de 1 s / 2 s / 3 s), consulte la Referencia de API del SDK de Android Happy Oyster.

8. Registro

Etiqueta Logcat del SDK: HappyOysterSDK. El SDK proporciona dos rutas de salida de registro independientes:

  • Logcat integrado (desactivado por defecto): el SDK escribe en Logcat solo cuando SDKConfig.logcatEnabled = true (valor predeterminado false, completamente silencioso). SDKConfig.logLevel filtra su nivel mínimo; el valor predeterminado INFO ya contiene los anclajes del ciclo de vida necesarios para reconstruir una sesión (inicialización, inicio de Travel/transiciones de estado/finalización, unión RTC/primer fotograma). Reduzca a WARN para solo errores, o eleve a DEBUG / VERBOSE para un diagnóstico más profundo. logLevel no afecta a logHandler.
  • Devolución de llamada del host logHandler (recomendado): recibe cada LogRecord del SDK (el flujo completo), totalmente independiente de logLevel / logcatEnabled, y puede reenviarse al propio sistema de registros del host (Logcat, archivos, plataformas de fallos, etc.). La devolución de llamada debe ser rápida y no bloqueante y no debe volver a llamar al SDK; las excepciones que lance se capturan silenciosamente; LogRecord.message está redactado y no contiene tokens Bearer, ticket ni tokens RTC en texto sin formato.
HappyOyster.initialize(
    context,
    SDKConfig(
        apiHost = "llm-xxxx.ap-southeast-1.maas.aliyuncs.com",  // required: your account's Bailian API Host
        model = "happyoyster-1.0",                          // required with no default: complete model name and version
        logcatEnabled = true,        // enable built-in Logcat (default false)
        logLevel = LogLevel.DEBUG,   // affects only the built-in Logcat
        logHandler = { record ->     // optional: forward to the host's own Logcat tag
            android.util.Log.d("MyApp/SDK", record.message, record.throwable)
        },
    ),
)
adb logcat -s HappyOysterSDK          # output only when built-in Logcat is enabled
adb logcat -s HappyOysterSDK MyApp    # also view the host App logs (replace MyApp with your tag)

Correlación de sesión y solicitud: el travelId (es decir, encryptedTravelId) en los registros del SDK se emite completo y es la clave de sesión enviada al servidor — inclúyalo al investigar una sesión o abrir un ticket para alinear los registros del cliente y del servidor. Cada línea de respuesta HTTP también lleva el requestId del servidor (el campo reqId=, mostrado como - cuando está ausente) para identificar una solicitud única; este ID aparece solo en los registros y nunca se expone en ningún valor de retorno público.

9. Ciclo de vida y memoria

  • Llame a initialize en Application.onCreate, una sola vez de forma global.
  • Reinicialización en inactividad (por ejemplo, para cambiar la región de la API o model) cierra el entorno de ejecución inactivo anterior y restablece los listeners registrados y el estado de las puertas de características; vuelva a registrar los listeners después de que retorne. La reinicialización es rechazada con 103004 mientras un Travel se está iniciando, está activo o está finalizando: siempre espere primero a endTravel(). El SDK nunca descarta un Travel activo como efecto secundario de initialize.
  • La experiencia está vinculada al ciclo de vida del host: llame a endTravel en el onDestroy de la Activity/Fragment (o en el onCleared del ViewModel) para asegurar que se liberen la conexión en tiempo real y los recursos.
  • Elimine la View devuelta por attachVideo() del diseño al finalizar (container.removeAllViews()) y llame a removeListener.
  • El SDK retiene únicamente el contexto de la aplicación; asimismo, no pase una Activity al SDK.

10. Corrutinas e hilos

  • Los métodos de negocio son suspend; llámelos en lifecycleScope / viewModelScope desde cualquier contexto de corrutina. El SDK coordina el estado y el trabajo RTC en su dispatcher principal mientras que HTTP permanece fuera del hilo principal.
  • Si la corrutina llamante se cancela, el SDK cancela la solicitud HTTP en curso de esa llamada, si la hay, y propaga CancellationException sin cambios en lugar de convertirla a SDKError; no la capture ni la ignore como un fallo operativo. La cancelación no revierte una solicitud que el servidor ya ha aceptado.
  • Llame a attachVideo() y sendCommand() en el hilo principal; una llamada fuera del hilo principal lanza sincrónicamente IllegalStateException.

11. Gestión de tokens

  • La Bearer API Key tiene un periodo de validez limitado; se recomienda asegurar que el token esté vigente antes de entrar a una experiencia. Al recibir onError(101002) (token expirado), vuelva a obtener la Bearer Key y llame a updateToken; no es necesario canjearla por un nuevo ticket.

12. Recuperación de errores

  • Para errores fatales: limpie el estado actual de la experiencia (incluyendo la eliminación de la View de video), notifique al usuario y permita el reinicio.
  • Para inestabilidad de red (106001) y anomalías temporales del servicio ascendente (106003): se puede realizar un número limitado de reintentos.
  • Un 100002 sincrónico desde la inicialización significa que model está vacío; proporcione el nombre y la versión completos del modelo, luego inicialice nuevamente. Si un model no vacío tiene el nombre o versión incorrectos, ha sido retirado, aún no se ha publicado o no está autorizado, la puerta de enlace normalmente devuelve AccessDenied, mapeado a 106003; utilice SDKError.raw y verifique que el model, API Host, API Key, cuenta y región coincidan.

(Para la clasificación fatal / no fatal, consulte la sección de códigos de error de la Referencia de API del SDK de Android Happy Oyster.)

13. Adaptación de modos

  • Utilice el mode devuelto por startTravel para decidir qué capacidades de interacción exponer: dirección y actuación utilizan la instrucción de texto sendInstruct (actuación también utiliza aspectRatio para la orientación del reproductor y oculta el rebobinado); el modo aventura utiliza el comando de control sendCommand.
  • En el modo aventura, puede utilizar la sobrecarga startTravel(ticket, maxExperienceTimeSec) para limitar la duración máxima de esta experiencia (segundos; la sesión finaliza automáticamente al alcanzarse). Los valores permitidos están configurados por el servidor (actualmente 60 / 90 / 120, valor predeterminado 60); dirección y actuación ignoran el valor (el servidor lo ignora y devuelve null). Pasar un valor no soportado hace que el servidor devuelva 400000 y este inicio falle. Consulte la Referencia de la API para obtener detalles sobre los parámetros.

actuación: Configuración de la orientación del reproductor desde aspectRatio

StartTravelData.aspectRatio es no null solo para actuación: "9:16" (vertical, el valor predeterminado del servidor cuando se crea el mundo) o "16:9" (horizontal); es null para aventura y dirección. El lienzo se fija cuando se crea el mundo (especificado por su servidor a través de la Open API), por lo que para el cliente es un resultado de solo lectura. El SDK conserva los valores no reconocidos textualmente, por lo que el host debe tratar cualquier valor que no reconozca como null y recurrir a su propia orientación predeterminada.

Momento: decida la orientación del contenedor de reproducción después de que startTravel() retorne y antes de llamar a attachVideo() y montar el SurfaceView devuelto en su diseño. El SDK solo comienza a vincular y renderizar el flujo remoto una vez que la vista del host está adjunta, por lo que decidir la orientación en ese punto todavía precede al primer fotograma renderizado. El valor se entrega con el valor de retorno de startTravel() y no se garantiza que llegue antes de que el SDK se una al canal de comunicación en tiempo real — solo tiene que aplicarse antes de attachVideo().

Consecuencia: la vista remota está vinculada con un modo de renderizado clip-to-fill — un contenedor cuya orientación no coincide con aspectRatio recorta la imagen (por ejemplo, una transmisión vertical 9:16 colocada en un contenedor 16:9 pierde la mayor parte de su parte superior e inferior) en lugar de añadir bandas negras.

// After startTravel() returns and before attachVideo(): size the container from aspectRatio first
val ratio: Float = when (travel.aspectRatio) {   // width / height
    "9:16" -> 9f / 16f                           // portrait (the Acting server-side default)
    "16:9" -> 16f / 9f                           // landscape
    else -> HOST_DEFAULT_RATIO                   // null or unrecognized: fall back to your own default orientation
}
// Apply `ratio` to the container, for example:
//   - Compose: Modifier.fillMaxWidth().aspectRatio(ratio)
//   - Views: put the container in a ConstraintLayout, full width, height driven by the ratio (height=0dp in XML)
binding.videoContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
    dimensionRatio = ratio.toString()
}

// Only once the container orientation is settled, mount the video View returned by the SDK
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)

NotaCuando la actuación no está habilitada para la cuenta (o la especificación está desactivada), tanto la creación como la unión al mundo son rechazadas por el servidor: startTravel falla con 403007 (rechazado antes de crear el Travel), lo cual el SDK transmite textualmente. No reintente este código como capacidad llena; en su lugar, solicite la habilitación.

14. Ejemplo completo (fragmento de ViewModel + Activity)

class TravelViewModel : ViewModel() {

    private val listener = object : HappyOysterListener {
        override fun onStatusChanged(status: TravelStatusValue) {
            _status.value = status
        }
        override fun onError(error: SDKError) {
            _error.value = error
        }
    }

    init { HappyOyster.addListener(listener) }

    fun start(ticket: String) = viewModelScope.launch {
        try {
            val travel = HappyOyster.startTravel(ticket)
            _travel.value = travel
        } catch (e: SDKError) {
            _error.value = e
        }
    }

    fun send(text: String) = viewModelScope.launch {
        runCatching { HappyOyster.sendInstruct(text) }
    }

    fun stop() = viewModelScope.launch { runCatching { HappyOyster.endTravel() } }

    override fun onCleared() {
        HappyOyster.removeListener(listener)
        viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
    }
}

// Mount the video in the Activity
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// On end
binding.videoContainer.removeAllViews()

15. Guía de solución de problemas de DevOps

Cuando algo sale mal durante la integración (no se puede entrar a un Travel, pantalla negra sin video, caída de transmisión a mitad de sesión, anomalías de pausa/reanudación, ...), abrir los registros del SDK es la forma más rápida de localizar el problema. Este capítulo ofrece un flujo estándar de solución de problemas, útil tanto para su propia verificación como para proporcionarnos suficiente información en una sola ronda.

15.1 Habilitación de registros

Elija una salida de registro según sea necesario durante la resolución de problemas (consulte §8 Registros): para una verificación rápida, configure logcatEnabled = true y léalo con adb logcat, elevando logLevel a DEBUG o VERBOSE al realizar diagnósticos (el valor predeterminado INFO ya incluye la línea temporal completa del ciclo de vida; reduzca a WARN para solo errores); para alimentar su propio sistema, utilice logHandler para recibir cada LogRecord (sin verse afectado por logLevel) y escríbalo en su archivo o plataforma de fallos.

15.2 ID de correlación de sesión: travelId

El travelId (es decir, StartTravelData.encryptedTravelId) en los registros del SDK se emite en su totalidad. Es el identificador único que recorre una sesión y la clave de sesión enviada al servidor. Es la clave para alinear los registros del cliente con los registros de sesión del servidor — incluya siempre el travelId de la sesión fallida al reportar un problema.

15.3 Recopilación de registros

Al reproducir el problema, guarde los registros del SDK en un archivo:

# Clear history, reproduce the issue, then capture SDK logs to a file
adb logcat -c
adb logcat -s HappyOysterSDK > happyoyster-sdk.log
# To also see your own app logs (replace MyApp with your tag):
adb logcat -s HappyOysterSDK MyApp > happyoyster-sdk.log

15.4 Qué incluir al reportar un problema

Para evitar múltiples idas y vueltas, adjunte lo siguiente:

Elemento

Notas

Versión del SDK

HappyOyster.VERSION

Sesión travelId

el encryptedTravelId de la sesión fallida (consulte 15.2)

Hora de ocurrencia

la hora aproximada (con precisión de minutos es suficiente)

Código de error

el SDKError.code capturado (significados en la tabla de códigos de error de la Referencia de API)

Pasos de reproducción

ruta de acción + resultado esperado + resultado real

Entorno

modelo de dispositivo, versión de Android, red (WiFi/celular)

Archivo de registro

el registro HappyOysterSDK de 15.3 (se recomienda DEBUG/VERBOSE)

15.5 Sobre la redacción y la capacidad de compartir registros

Los registros del SDK están diseñados para ser seguros para compartir: las credenciales (token Bearer, ticket de Travel, token RTC), los identificadores internos de RTC y las URL de medios se redactan antes de escribirse, por lo que nunca aparecen en texto sin formato; travelId es un identificador de sesión (no una credencial) y se conserva completo únicamente para fines de correlación. Aun así, recomendamos transferir los archivos de registro a través de un canal de confianza en lugar de pegarlos públicamente en plataformas no controladas.