Todos los productos
Search
Centro de documentos

Alibaba Cloud Model Studio:Referencia de la API del SDK de HappyOyster para iOS

Última actualización: Oct 12, 2026

El punto de entrada del SDK de HappyOyster para iOS es el singleton a nivel de proceso HappyOysterEngine.shared. Todos los métodos de negocio son async throws y lanzan OysterSDKError en caso de fallo. Una sola experiencia es gestionada por un identificador OysterTravel, que abarca tres modos —adventure, directing y acting— tanto en UIKit como en SwiftUI.

Nota

  • Este documento es la descripción de funciones externas + referencia de interfaz para el Happy Oyster iOS SDK: explica los parámetros, tiempos, uso y ejemplos breves de cada tipo y método público, uno por uno.
  • Para el flujo de integración completo (configuración del proyecto, configuración de dependencias, coordinación del lado del servidor, ejecución de extremo a extremo), consulte la documentación del proyecto de ejemplo; no se repite aquí.

1. Conceptos básicos

Primero algunos términos, para facilitar la lectura del resto.

Concepto

Descripción

token

Token de autenticación HTTP (API Key temporal de Bailian). Su servidor lo obtiene mediante intercambio a través de la API de Bailian y lo entrega al cliente; se inyecta mediante updateToken(_:), y el SDK lo transmite como HTTP Bearer al solicitar la puerta de enlace. El SDK conserva únicamente el más reciente, no lo persiste ni lo renueva; tras su caducidad, debe volver a obtenerlo e inyectarlo.

ticket

Credencial de prueba de un solo uso. Su servidor la obtiene mediante intercambio a través de la plataforma abierta get-travel-credential (prefijo tk_, válida durante 30 minutos, de un solo uso) y se utiliza como argumento de createTravel(ticket:); deja de ser válida cuando la experiencia finaliza o caduca, y no puede reutilizarse.

Mundo

Un mundo de IA que contiene personajes y escenas. Creado y gestionado por su servidor; el SDK no interviene.

Travel

Una única experiencia en tiempo real, correspondiente a un identificador OysterTravel. De un solo uso; se invalida una vez que se alcanza un estado terminal, requiriendo un nuevo createTravel a través del motor.

Estado de la sesión

OysterTravelStatus: idle → prepare → running → pausing → paused (paused regresa a prepare → running mediante reconexión), más dos estados terminales ended / failed (véase §7).

Modo

adventure (comandos de dirección/vista/acción), directing (historia basada en texto) o acting (actuación de personaje basada en texto). El mode devuelto por start() determina la interfaz de usuario en consecuencia; consulte la §2 para conocer lo que admite cada modo.

2. Descripción general

Tras integrar este SDK, su aplicación puede ingresar a "mundos" generados en tiempo real por IA y disfrutar de una experiencia de video interactiva en tiempo real en tres modos: aventura, dirección o actuación: iniciar la experiencia → reproducción en tiempo real → interacción en tiempo real → control de proceso (pausa/reanudación/rebobinado/finalización) → callbacks de estado y error.

Cada modo admite un conjunto diferente de capacidades. Controle su interfaz de usuario basándose en el mode devuelto por start():

Capacidad

adventure

directing

acting

sendCommand (dirección/vista/acción)

Sí

No

No

sendInstruct (instrucción de texto)

No

Sí

Sí

pause() / resume()

No

Sí

Sí

rewind(toSec:)

No

Sí

No — ocultar el punto de entrada

end()

Sí

Sí

Sí

start(maxExperienceTimeSec:)

Aplicado

Ignorado

Ignorado

NotaLos mundos de acting priorizan el retrato: start() devuelve un aspectRatio (9:16 / 16:9) para la sesión. Úselo para elegir la orientación del reproductor y el tamaño del contenedor antes de extraer el flujo (véase §8).

import HappyOysterSDK; los puntos de entrada principales son dos tipos.

HappyOysterEngine — el punto de entrada de orquestación, un singleton a nivel de proceso HappyOysterEngine.shared.

Método / Propiedad

Descripción

initialize(config:)

Inicializa el entorno de ejecución y registra automáticamente el motor en tiempo real (una sola vez, antes de createTravel)

updateToken(_:)

Inyectar / actualizar el token de autenticación HTTP

createTravel(ticket:)

Crear un identificador de sesión con una credencial de un solo uso

cleanup()

Liberar recursos (se puede initialize nuevamente)

version

La versión actual del SDK

OysterTravel — el identificador de sesión para una experiencia, creado por createTravel(ticket:).

Método / Propiedad

Descripción

videoView / OysterVideoView(travel:)

Vista de reproducción (UIKit / SwiftUI)

events / status

Stream de push de cambio de estado + error / estado actual observable

start() / start(maxExperienceTimeSec:)

Conectar y reproducir (opcionalmente solicitar la duración máxima de una experiencia de aventura)

sendInstruct(content:)

Instrucción de texto en modo dirección

sendCommand(_:) / flushCommands()

Control del modo Aventura

pause() / resume()

Pausar / reanudar (directing y acting)

rewind(toSec:)

Rebobinar (solo en pausa)

end()

Finalizar (idempotente, asegúrese de llamarlo)

pauseLocalAudioCapture() / resumeLocalAudioCapture()

Liberar / restaurar temporalmente el micrófono

También hay algunas exportaciones auxiliares: OysterLog para tomar el control del registro interno del SDK; HappyOysterEngine.version para leer la versión; OysterVideoView es la vista de reproducción de SwiftUI (equivalente a videoView). Su uso se describe más adelante.

Ciclo de vida: inicializar → inyectar token → crear sesión → montar video + suscribirse a eventos → iniciar reproducción → interactuar → finalizar. El SDK no es responsable de crear ni gestionar mundos (tarea realizada por su servidor), ni expone detalles de comunicación en tiempo real de bajo nivel.

3. Inicio rápido

Un flujo completo desde la inicialización hasta la finalización, con comentarios paso a paso. Los detalles de cada API se encuentran en la §6.

import HappyOysterSDK

// 1) Initialize (as early as possible after app launch, once before createTravel)
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
    apiHost: "[workspace-id].[region].maas.aliyuncs.com",// For the API host, refer to the Bailian documentation https://www.alibabacloud.com/help/en/model-studio/base-url?spm=a2c4g.11186623.help-menu-2400256.d_0_0_4.171c56fftIRQRt&scm=20140722.H_3042998._.OR_help-T_cn~zh-V_1
    model: "happyoyster-1.0-adventure"                  // Versioned model name enabled for your account, required; see the Happy Oyster model documentation
))

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

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

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

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

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

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

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

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

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

NotaPara la configuración del proyecto, la configuración de dependencias (incluido el adaptador AliRTC requerido y el binario del proveedor), la coordinación del lado del servidor y una ejecución de extremo a extremo, consulte la documentación del proyecto de ejemplo.

4. Requisitos

Elemento

Requisito

SO mínimo

iOS 15.0+ (todos los tipos públicos están marcados con @available(iOS 15.0, *))

Lenguaje

Swift (async/await)

Concurrencia

Acceso desde el hilo principal (los tipos de entrada están marcados con @MainActor)

Red

Se requiere acceso a la red pública

Permisos

Info.plist debe incluir NSMicrophoneUsageDescription (ver abajo)

Permiso de micrófono: Una experiencia de video interactiva en tiempo real requiere un canal de audio/video en tiempo real bidireccional (enlace ascendente + enlace descendente), por lo que el SDK ocupa el micrófono local mientras se ejecuta; esto no constituye una grabación. Info.plist debe proporcionar NSMicrophoneUsageDescription; de lo contrario, iniciar la captura en tiempo real provocará un fallo. Cuando necesite acceso exclusivo al micrófono (por ejemplo, reconocimiento de voz), utilice pauseLocalAudioCapture() / resumeLocalAudioCapture() para cederlo y restaurarlo temporalmente (consulte la §6.2).

5. Integración y autenticación

5.1 Integración y dependencias

import HappyOysterSDK es todo lo que necesita a nivel de código. El SDK se distribuye como un binario precompilado (xcframework) a través de subespecificaciones de CocoaPods, publicado en el Trunk público de CocoaPods — declare las dependencias en su Podfile:

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

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

NotaAliVCSDK_ARTC es obligatorio siempre que incluya HappyOysterSDK/StreamAliRTC: si falta, el SDK recurre silenciosamente a Loopback — puede conectarse y alcanzar running, pero muestra una pantalla negra sin error.

La configuración del proyecto y el flujo de inicialización de extremo a extremo son manejados por el proyecto de ejemplo; consulte la documentación del proyecto de ejemplo. Este documento se centra en la interfaz misma.

5.2 Modelo de autenticación

El SDK no obtiene ni actualiza tokens, manteniendo la ligereza. La autenticación tiene dos capas y el integrador gestiona sus ciclos de vida:

  1. Token de autenticación HTTP (API Key temporal de Bailian): Su servidor lo obtiene mediante intercambio a través de la API de Bailian y lo entrega al cliente; se inyecta mediante updateToken(_:). Algunos servicios internos del SDK llaman directamente a la puerta de enlace de Bailian, portando este token para la autenticación; por lo tanto, debe ser una API Key temporal emitida por Bailian, no un token de su propio servicio de negocio. El SDK conserva únicamente el más reciente, no lo persiste ni lo renueva; tras su caducidad, debe volver a obtenerlo e inyectarlo.
  2. Credencial de prueba de un solo uso ticket: Su servidor la obtiene mediante intercambio a través de la plataforma abierta get-travel-credential (prefijo tk_, válida durante 30 minutos, de un solo uso) y se utiliza como argumento de createTravel(ticket:); deja de ser válida cuando la experiencia finaliza (de forma normal o anómala) o caduca, y no puede reutilizarse.

NotaAK, claves de firma y otras credenciales de altos privilegios existen solo en su servidor; el SDK del cliente nunca las toca. Lo que recibe el cliente es siempre una API Key temporal de corta duración.

NotaFeature Gate (interruptor remoto / actualización forzada): El servidor puede deshabilitar remotamente el SDK o establecer una versión mínima compatible. Mientras esté deshabilitado, las llamadas protegidas (start / pause / resume / rewind / sendInstruct / sendCommand) se rechazan con 108001 y cualquier experiencia en curso es finalizada por el SDK (consulte la §9). OysterSDKError.raw contiene un motivo legible por humanos; solicite al usuario que actualice cuando la versión sea demasiado baja.

Manejo de la caducidad del token: Después de que el token de autenticación HTTP anterior caduque, solicite inmediatamente un nuevo token a su servidor e inyéctelo mediante updateToken(_:). Hay dos lugares donde necesita determinar si el token ha caducado:

  1. Al llamar a APIs como engine.createTravel o travel.start, maneje el error y verifique los tipos de error de token caducado/inválido (101001 / 101002); después de inyectar un nuevo token, vuelva a llamar a la API correspondiente.
  2. Al escuchar el evento .error de OysterTravelEvent, verifique los tipos de error de token caducado/inválido, vuelva a solicitar el token e inyéctelo.

6. Referencia de API

Hay dos tipos de entrada, ambos @MainActor y @available(iOS 15.0, *). Los métodos de negocio son async throws y lanzan OysterSDKError en caso de fallo; los métodos con valores de retorno están todos marcados con @discardableResult.

6.1 HappyOysterEngine

Un singleton a nivel de proceso, el punto de entrada de orquestación. init no es público — use siempre HappyOysterEngine.shared; no lo instancie usted mismo (el motor subyacente en tiempo real también es un singleton de proceso).

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

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

initialize(config:)

  • Propósito: Inicializar el entorno de ejecución y registrar automáticamente el motor en tiempo real (no se requiere registro manual por parte del host).
  • Parámetros: config.apiHost es la URL de la puerta de enlace de Bailian, no la de su servidor de negocio, y es obligatoria; en entornos de prelanzamiento o prueba, debe pasar explícitamente la puerta de enlace correspondiente; de lo contrario, las solicitudes fallarán (por ejemplo, 106001, dominio no resuelto). config.model es el nombre del modelo versionado habilitado para su cuenta y también es obligatorio sin valor predeterminado: Happy Oyster se divide en submodelos por modo, por lo que el SDK no puede inferir cuál utilizar. Ambos deben pertenecer a la misma cuenta y región que el token inyectado. Otros campos se detallan en la §8 OysterConfig.
  • Un modelo sirve a un mode: cada modelo por modo tiene su propia ruta de puerta de enlace, por lo que una sola llamada a initialize solo sirve mundos de ese único mode. Si su aplicación ofrece mundos en varios modos, simplemente llame a initialize() nuevamente con el modelo correspondiente antes de entrar a un mundo de un modo diferente; mientras está inactivo, prevalece la configuración más reciente, no se requiere cleanup() y se conserva el token inyectado; mientras un Travel está en curso, la llamada se ignora, así que invoque end() primero. Un modelo que no coincida con el mode del mundo es rechazado por la puerta de enlace con AccessDenied, normalizado a 106003.
  • Cuándo usar: Llamar una vez antes de createTravel, lo antes posible después del inicio de la aplicación.
  • Devuelve: Bool — indica si la config que usted proporcionó surtió efecto. Es false en dos casos: la configuración no es válida (apiHost / model en blanco o sin formar una URL de puerta de enlace válida), o hay un Travel en curso, por lo que se ignoró la llamada. En ambos casos, el entorno de ejecución permanece sin cambios.
  • Nota: Mientras está inactivo, llamarlo nuevamente reconfigura el entorno de ejecución con la nueva configuración (cambiar apiHost / model no requiere cleanup(), y se conserva el token inyectado); es una operación nula con solo una advertencia mientras un Travel está en curso, así que invoque end() primero. Una configuración no válida deja el entorno de ejecución sin cambios. No utilice isReady para determinar si una re-initialize tuvo éxito: si fue rechazada, la configuración anterior sigue vigente y isReady permanece true; isReady responde a "¿está el motor usable ahora?", mientras que el valor de retorno responde a "¿surtió efecto la configuración que acabo de pasar?".

OysterLog

  • Propósito: Configurar y tomar el control del registro interno del SDK, imprimiéndolo en su propio módulo de registro. Proporciona setMinimumLevel(_:) para establecer el nivel y setHandler(_:) para salida personalizada (véase el ejemplo de §3).

updateToken(_:)

  • Propósito: Inyectar / actualizar el token de autenticación HTTP (clave de API temporal de Bailian, §5.2).
  • Cuándo usarlo: Después de initialize, puede llamarse en cualquier momento; vuelva a intercambiar y llame nuevamente después de que el token caduque o tras recibir un error relacionado con la autenticación (101001 / 101002).
  • Nota: Es una operación nula con una advertencia cuando no se ha initialized.

createTravel(ticket:)

  • Propósito: Crear un único identificador de sesión con un ticket de un solo uso.
  • Parámetro: ticket es una credencial de un solo uso; una vez creada, se considera ocupada para esta experiencia.
  • Cuándo usarlo: Llame antes de cada nueva experiencia; el identificador devuelto aún no está conectado y debe llamar a travel.start() después. El video se toma del identificador devuelto (véase §6.2).
  • Nota (throws sincrónico): lanza 100001 si no se ha ejecutado initialize; lanza 103004 si se llama nuevamente antes de que el Travel anterior haya ejecutado end() (cada motor permite solo un Travel activo a la vez).

cleanup()

  • Propósito: Liberar recursos del SDK (finalizar el travel activo, configuración de tiempo de ejecución, token).
  • Cuándo usar: Al salir completamente del SDK o necesitar cambiar la config.
  • Nota: async — internamente termina determinísticamente el travel activo actual mediante end() primero, luego desmantela el entorno de ejecución, sin dejar operaciones fire-and-forget. Después de la liberación, puede ejecutar initialize nuevamente.

6.2 OysterTravel

El identificador de sesión creado por createTravel; es de un solo uso y se invalida al alcanzar un estado terminal (end / finalización del lado del servidor / fallo), lo que requiere invocar nuevamente createTravel a través del motor. También es un ObservableObject (@Published status, puede controlar directamente SwiftUI).

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

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

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

Vista de video videoView / OysterVideoView(travel:)

  • Propósito: El punto de entrada de renderizado para la imagen remota — "el SDK proporciona la vista, el host la coloca". Para UIKit, tome travel.videoView; para SwiftUI, use OysterVideoView(travel:).
  • Cuándo usarlo: Disponible tan pronto como se crea el identificador (el acceso repetido devuelve la misma vista); móntela en cualquier jerarquía y, una vez que el motor esté listo, se renderiza automáticamente. Montarla antes o después de start() funciona en ambos casos, sin pantalla negra.
  • Nota: Cuando finaliza la sesión, el SDK libera automáticamente el enlace de renderizado; elimine la vista de la jerarquía según sea necesario.

Eventos y estado events / status / isEnded

  • Propósito: events es el flujo push de cambios de estado + errores; status es el estado externo actual observable; isEnded es un indicador legible sincrónicamente para el estado terminal.
  • Cuándo usarlo: Se recomienda comenzar a consumir events antes de start(), para evitar perder los primeros estados.
  • Nota: Cada acceso a events devuelve un flujo independiente, compatible con múltiples suscripciones; cancelar la suscripción equivale a finalizar la iteración for await (o destruir la Task retenida). En SwiftUI, puede observar directamente status con @StateObject/@ObservedObject (los errores siguen llegando a través de events). Consulte la §7.

start() / start(maxExperienceTimeSec:)

  • Propósito: Usar el ticket capturado en el momento de la creación para intercambiarlo por la configuración de travel + unión RTC y conectarse para reproducir. En caso de éxito, el SDK establece automáticamente la conexión en tiempo real y comienza el sondeo de estado interno, exponiendo el estado a través de events.
  • Parámetro: maxExperienceTimeSec (opcional) — solicita la duración máxima (en segundos) de esta experiencia de aventura. El valor se envía al servidor tal cual; los valores permitidos, la duración efectiva real y el momento de finalización automática son decididos por el servidor; el SDK no realiza validación local. Pasar nil (o llamar a start() sin parámetros) utiliza la duración predeterminada del servidor. El modo Dirección ignora este parámetro. Cuando se agota el tiempo, el servidor finaliza la experiencia y el host recibe el estado terminal ended a través de events (igual que una finalización del lado del servidor, consulte la §7).
  • Devuelve: OysterStartTravelData (mode / version / encryptedTravelId, etc., véase §8), que determina la interfaz de usuario de interacción.
  • Errores: 401010/401011 (credencial no válida o ya utilizada), 403002 (mundo no listo), 403007 (especificación de servicio no habilitada, por ejemplo, actuación), 429001/429002 (límite de concurrencia / capacidad agotada), 500001 (fallo de recurso/servidor), 103004 (inicio concurrente).
  • El mode del mundo del ticket debe coincidir con el model pasado a initialize (responsabilidad del llamante): con modelos por modo, cada modelo es su propia ruta de puerta de enlace, y start() envía el ticket a la ruta del modelo actualmente inicializado****. El SDK no verifica ni puede verificar esto por usted de antemano — mode se entrega mediante la respuesta a esta misma llamada de start() (OysterStartTravelData.mode); antes de la llamada, el SDK solo posee un ticket opaco y un nombre de modelo, sin ningún modo contra el cual comparar, e inferir el modo a partir del nombre del modelo sería adivinar un esquema de nomenclatura propiedad del servidor, lo cual el SDK no hace. Por lo tanto: antes de entrar en un mundo de un modo diferente, llame nuevamente a initialize() con el modelo correspondiente (mientras está inactivo, prevalece la configuración más reciente — no se requiere cleanup() y se conserva el token; mientras un Travel está en curso, la llamada se ignora, así que ejecute end() primero). En caso de discrepancia, esta llamada de start() falla en la puerta de enlace; al diagnosticar, verifique primero que el model actual y el mode del mundo del ticket pertenezcan juntos, luego consulte los códigos de error de credenciales anteriores.
  • Nota (finalización automática por ausencia de flujo): El servidor envía un "tiempo de espera sin flujo" (predeterminado ~30 s). Si, tras conectarse, no se recibe ningún flujo dentro de esa duración (nunca alcanza running), el SDK finaliza automáticamente la experiencia, transiciona a failed y expone 105006 mediante .error de events (fatal; manéjelo regresando a la pantalla previa al inicio, sin necesidad de medir el tiempo usted mismo).

pause() / resume()

  • Propósito: Pausar / reanudar la experiencia (idempotente).
  • Cuándo usarlo: Soportado por mundos de directing y acting; no soportado por adventure. Use el mode devuelto por start() para decidir de antemano si mostrar un botón de pausa. pause requiere que el estado actual sea running; resume requiere paused.
  • Errores: 103001 (sin experiencia activa), 103002 (estado/versión no permitidos), 103003 (discordancia de modo).

rewind(toSec:)

  • Propósito: Rebobinar a los segundos especificados. En caso de éxito, el SDK se reincorpora automáticamente con el rtcConfig original y reanuda la reproducción.
  • Cuándo usarlo: Solo puede iniciarse en el estado paused, y solo por mundos dedirecting — acting y adventure no admiten rebobinado, así que oculte el punto de entrada de rebobinado en esos modos y no lo llame.
  • Errores: 103001, 103002.

end()

  • Propósito: Finalizar la experiencia (idempotente, puede llamarse repetidamente). Tras una llamada exitosa o una salida anómala, el SDK desconecta automáticamente la conexión en tiempo real, detiene el sondeo y libera todos los recursos de la sesión; el ticket se invalida simultáneamente y el identificador entra en un estado terminal.
  • Cuándo usar / Nota: Ya sea que el usuario salga activamente o que la experiencia finalice pasivamente (caducidad del temporizador, recepción de .ended/.failed, destrucción de la página), asegúrese de que se ejecute una única llamada a end(); de lo contrario, los recursos remotos podrían no liberarse oportunamente. Se recomienda canalizar todas las rutas de salida hacia el mismo método de limpieza idempotente.

sendInstruct(content:) (modo dirección)

  • Propósito: Enviar una instrucción de texto para impulsar la trama.
  • Cuándo usarlo: Modo dirección; se envía directamente cuando está en running, se almacena en caché cuando está en paused y se reenvía con el primer fotograma tras reanudar a running mediante reconexión.
  • Errores: 103001, 103002, 103003 (llamado en modo aventura), 403004 (moderación de contenido), 404000 (travel no encontrado).

sendCommand(_:) / flushCommands() (modo aventura)

  • sendCommand: Envía comandos de control de dirección/vista/acción (consulte la §8 OysterAdventureCommand; fire-and-forget, sin retorno, no lanza excepciones). Efectivo únicamente en modo aventura cuando está running. La entrada externa puede ser de alta frecuencia en cada fotograma, y el SDK aplica limitación internamente (muestreo de última victoria, fusión de fotogramas a la tasa de línea RTC); el host no necesita aplicar limitación por sí mismo. Tenga en cuenta que la respuesta del servidor a los comandos tiene latencia inherente, por lo que el momento efectivo real no es fijo.
  • flushCommands: Llamado en el momento de "liberación de tecla / liberación de entrada", reenviando inmediatamente el último comando ya en espera en la cola; es una operación nula pura cuando no hay ningún comando pendiente, sin generar ningún comando nuevo.
  • Errores (todos expuestos a través del .error de events, no mediante throws): sin experiencia activa 103001; llamado en modo dirección 103003; canal en tiempo real no listo / envío fallido 105004.

pauseLocalAudioCapture() / resumeLocalAudioCapture()

  • Propósito: Liberar / restaurar temporalmente la ocupación del SDK sobre la captura del micrófono local.
  • Cuándo usarlo: Cuando algo como el reconocimiento de voz necesita acceso exclusivo al micrófono, ejecute pause primero y resume después.

7. Eventos y estado

Los eventos están adjuntos a OysterTravel.events y son el canal de push activo del SDK hacia usted, utilizados para exponer situaciones no desencadenadas por sus propias llamadas (por ejemplo, problemas con la conexión en tiempo real gestionada internamente o el sondeo de estado).

var events: AsyncStream<OysterTravelEvent> { get }

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

Hay dos estilos de consumo, elija uno:

  • SwiftUI: OysterTravel es un ObservableObject; use directamente @StateObject/@ObservedObject para observar status y controlar la interfaz de usuario; los errores siguen proviniendo de events.
  • Imperativo / UIKit: en un Task, use for await event in travel.events { ... } y switch sobre .statusChanged / .error; cancele el Task retenido al finalizar.
let task = Task {
    for await event in travel.events {
        switch event {
        case .statusChanged(let status): render(status)   // see table below
        case .error(let error):          handle(error)     // error.code / error.kind, see §9
        }
    }
}
// When done: task.cancel()

OysterTravelStatus (5 estados de proceso + 2 estados terminales):

Status

Descripción

Manejo típico

idle

después de crear, antes de iniciar

—

prepare

conectando / reconectando (conexión / reconexión interna)

mostrar aviso de conexión / reconexión

running

stream listo, interactivo (reproducción interna)

mostrar imagen y controles

pausing

pausa aceptada, esperando confirmación del servidor

mostrar "pausando…"

paused

pausado (confirmado)

mostrar estado pausado (solo directing muestra la entrada de rebobinado)

ended

finalizado (finalización activa o finalización del lado del servidor). Terminal

finalizar y cerrar la página

failed

fallido. Terminal

mostrar error y finalizar

NotaDespués de entrar en ended / failed, la sesión ha terminado y todas las operaciones de sesión (pause/resume/sendCommand…) ya no surten efecto. Las devoluciones de llamada pueden activarse en el hilo principal, por lo que puede actualizar la interfaz de usuario directamente.

NotaDurante el sondeo interno de estado, el SDK reporta automáticamente latidos de estado de extracción/reproducción del cliente (conectando / reproduciendo / pausado / reconectando / desconectado), de modo que el servidor pueda distinguir entre los estados del flujo y los del cliente; este es un comportamiento puramente interno del SDK, del cual el host no necesita tener conocimiento ni participar.

8. Modelos de datos

NotaTodos los tipos públicos están marcados con @available(iOS 15.0, *). Los valores de retorno y parámetros a continuación son salidas del SDK, construidos internamente en el lugar con tipos nativos de Swift (Date / TimeInterval / OysterTravelStatus); no Codable son ni exponen detalles de transmisión (snake_case) — la decodificación de la transmisión ocurre dentro del SDK.

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

public enum OysterLogLevel: Int, Comparable, CaseIterable, Sendable {
    case debug, info, warning, error
}

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

// Open string value (preserves unknown values for server-side extension; encoded/decoded as a bare JSON string "running")
public struct OysterRawValue: RawRepresentable, Equatable, Hashable, Codable, Sendable {
    public let rawValue: String
    public init(rawValue: String) { self.rawValue = rawValue }
}
public typealias OysterModeValue = OysterRawValue
public extension OysterModeValue {
    static let adventure = OysterModeValue(rawValue: "adventure")
    static let directing  = OysterModeValue(rawValue: "directing")
    static let acting     = OysterModeValue(rawValue: "acting")
}

// start() return (SDK output, not Codable)
public struct OysterStartTravelData: Equatable, Sendable {
    public let encryptedTravelId: String  // identifier for this experience
    public let encryptedWorldId: String
    public let mode: OysterModeValue       // adventure / directing / acting
    public let playUrl: String?            // currently always returned as null by the server
    public let firstFrame: String?         // first-frame image URL, may be null (produced asynchronously)
    public let version: String             // world version identifier (diagnostics; drive interaction UI from `mode`)
    public let aspectRatio: String?        // "9:16" / "16:9"; only set for acting worlds, nil otherwise
}

// Control API returns (SDK output, not Codable; status is the external enum OysterTravelStatus)
public struct OysterTravelStateData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus }
public struct OysterRewindTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let resumedAtSec: TimeInterval }
public struct OysterEndTravelData: Equatable, Sendable { public let encryptedTravelId: String; public let status: OysterTravelStatus; public let endedAt: Date; public let duration: TimeInterval }
public struct OysterSendInstructData: Equatable, Sendable { public let encryptedTravelId: String; public let content: String; public let accepted: Bool }

// Adventure-mode control command (strongly typed enums; values aligned with the internal WorldControlParams)
public struct OysterAdventureCommand: Equatable, Sendable {
    public enum Translation: String { case none, front, back, left, right, frontLeft, frontRight, backLeft, backRight }
    public enum Rotation: String { case none, mouseUp, mouseDown, mouseLeft, mouseRight, mouseUpLeft, mouseUpRight, mouseDownLeft, mouseDownRight }
    public enum Interaction: String { case none, jump, attack, crouch, sprint }
    public let translation: Translation   // movement for this command; submit every frame while held, then stop
    public let rotation: Rotation          // view rotation for this command; submit every frame while held, then stop
    public let interaction: Interaction    // one-shot action for this command; submit once
    public init(translation: Translation = .none, rotation: Rotation = .none, interaction: Interaction = .none)
    public init(_ params: WorldControlParams)   // convenient bridge from the default control WorldControlParams (consistent rawValue)
}

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

// Typed semantic view of error codes: local codes are named cases; server-side 4xxxxx/5xxxxx converge to .server(code:)
public enum OysterErrorKind: Equatable, Sendable {
    case notInitialized, tokenMissing, tokenInvalid, noActiveTravel, invalidState, sendCommandInDirecting
    case concurrentTravel, realtimeConnectFailed, realtimeJoinTimeout, firstFrameTimeout
    case channelNotReady, callbackTimeout, localNetwork, responseDecodeFailed
    case streamAutoEnd, proxyOrUnrecognized, featureGateDisabled
    case server(code: Int), unknown(code: Int)
}

Nota

  • Nota: los rawValues de los comandos están en lower camelCase (por ejemplo, front / mouseLeft / jump); los valores de enumeración anteriores son autoritativos.
  • Nota: mode es externamente adventure (wander) / directing (historia) / acting (juego de roles); la definición de OysterModeValue es autoritativa.
  • Nota: aspectRatio es una cadena abierta (actualmente 9:16 / 16:9, pueden añadirse más). Analícela como width:height y compare la proporción en lugar de coincidir con valores conocidos.

9. Códigos de error

El SDK reporta los errores uniformemente como OysterSDKError, y el tipo siempre se distingue por code; no determine el tipo basándose en "la ruta por la que llegó el error": el mismo code puede ser lanzado por un método de negocio (async throws) o expuesto mediante .error de events. Para la coincidencia tipada, utilice error.kind (consulte la §8 OysterErrorKind).

Códigos de error: servidor 4xxxxx/5xxxxx, cliente-local 1xxxxx.

Códigos de error del servidor (comunes)

code

Significado

Manejo sugerido

400000

Parámetros no válidos (valor de enumeración no válido, etc.)

Verifique los parámetros de la solicitud o la versión del SDK

401010

Credencial de experiencia (ticket) no válida o caducada

Solicitar al servidor que vuelva a emitir la credencial

401011

Credencial de experiencia (ticket) ya utilizada

Credencial de un solo uso; volver a emitir

403001

El mundo no existe, fue eliminado o no pertenece al desarrollador actual (incluido un mundo eliminado después de emitir la credencial)

Seleccionar un mundo válido nuevamente

403002

Estado del mundo no listo

Espere a que el mundo esté listo antes de comenzar

403003

La API solo permite la clave de API principal

No se puede usar una clave temporal para esta API

403004

Contenido de entrada rechazado por moderación de contenido; aplica a instrucciones de texto de sendInstruct

Cambie la entrada e inténtelo de nuevo

403007

La especificación de servicio solicitada no está habilitada

No reintente como si la capacidad estuviera llena; cambie a una especificación habilitada (típicamente: la cuenta no tiene especificación de actuación)

403008

Configuración de capacidad temporalmente no disponible

Reintentar más tarde

404000

El recurso no existe (propiedad de mundo/wander o sin artefacto)

Verificar ID / estado

409000

La solicitud entra en conflicto con el estado actual del recurso

Verifique el estado de la experiencia

429001

Límite de concurrencia alcanzado para esta especificación

Reintente después de que finalice una sesión existente (no confundir con 500001)

429002

Capacidad disponible insuficiente

Reintentar más tarde

500000

Error interno del sistema

Reintentar más tarde / reportarlo

500001

Asignación de recursos de inferencia o fallo interno del servicio

Reintentar más tarde

Códigos de error del cliente local

code

Significado

El SDK finaliza automáticamente la sesión

Manejo sugerido

100001

Llamado antes de la inicialización del SDK; también cubre un initialize que no surtió efecto porque apiHost/model estaba en blanco o era inválido

No (lanzado sincrónicamente, rechazando esta llamada)

Ejecute initialize primero y verifique que tanto apiHost como model estén configurados correctamente

101001

Token de autenticación HTTP no inyectado

No

reintentar después de updateToken

101002

Token de autenticación HTTP no válido / rechazado

No

volver a intercambiar el token y luego reintentar updateToken

103001

No hay experiencia activa actualmente

No (rechazando esta llamada)

createTravel + start primero

103002

El estado/versión actual no permite esta operación

No (rechazando esta llamada)

verificar estado de la experiencia / version

103003

Desajuste de modo (por ejemplo, sendCommand llamado en un mundo no adventure)

No (rechazando esta llamada)

elegir la API correcta por mode, ver la tabla de capacidades de la §2

103004

Creación/inicio concurrente de experiencia

No (lanzado sincrónicamente)

serializar llamadas, end la sesión anterior primero

105001

Error de conexión en tiempo real

Sí

finalizar y reiniciar

105002

Tiempo de espera de unión en tiempo real agotado

Sí

finalizar y reiniciar

105003

Tiempo de espera agotado esperando el primer fotograma de video

Sí

finalizar y reiniciar

105004

Canal en tiempo real no listo / error de envío

depende (fallo de envío activo; el heartbeat es solo de reporte)

enviar después de running

105005

Tiempo de espera de callback agotado (predeterminado 30s)

No

reintentar y aumentar callbackTimeoutMs si es necesario

105006

Sin stream después de unirse; el SDK finaliza automáticamente la experiencia

Sí

finalizar y reiniciar

106001

Error de red local

No

reintentable

106002

Error al analizar la respuesta

depende

actualizar el SDK / reportar

106003

Código de error no reconocible / código de error de cadena de proxy

No

reintentable

108001

Deshabilitado remotamente por el interruptor de funciones del servidor (apagado total o versión demasiado baja; motivo en raw)

Sí

Seguir el motivo en raw; guiar al usuario a actualizar cuando la versión sea demasiado baja

Cómo juzgar la fatalidad: La fatalidad ya no se expone como un campo booleano (OysterSDKError no tiene isFatal). Semánticamente, "fatal" significa específicamente si el SDK termina activamente la sesión (desconectar RTC, liberar toda la sesión) —

  • Errores que finalizan automáticamente la sesión (por ejemplo, 105001/105002/105003/105006/108001): el host detecta esto a partir del estado terminal de la máquina de estados (status → failed, expuesto mediante .statusChanged de events) y regresa a la pantalla previa a "iniciar experiencia" en consecuencia, sin necesidad de evaluar por sí mismo la gravedad del error.
  • Errores de rechazo de llamada (100001/103001/103002/103003/103004): se lanzan sincrónicamente / la llamada se rechaza cuando usted llama activamente; no terminan la sesión.
  • Otros errores no fatales (por ejemplo, 101001/101002/105005/106001/106003): no terminan la sesión; reintente según lo sugerido o continúe después de volver a inyectar el token.

Nota106001 puede tener dos causas: un error de red local o un apiHost incorrecto. Si reintentar no recupera la solicitud, verifique si apiHost está configurado correctamente.

NotaLa aparición de 106003 después de configurar model suele indicar que el nombre o la versión del modelo son incorrectos, o que no están habilitados para su cuenta: verifique el modelo junto con apiHost y el token, los cuales deben pertenecer todos a la misma cuenta y región; la puerta de enlace rechaza cualquier discrepancia con AccessDenied, normalizado a este código.