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 |
ticket | Credencial de prueba de un solo uso. Su servidor la obtiene mediante intercambio a través de la plataforma abierta |
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 |
Estado de la sesión |
|
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 |
|
|
|
|---|---|---|---|
| Sí | No | No |
| No | Sí | Sí |
| No | Sí | Sí |
| No | Sí | No — ocultar el punto de entrada |
| Sí | Sí | Sí |
| 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 |
|---|---|
| Inicializa el entorno de ejecución y registra automáticamente el motor en tiempo real (una sola vez, antes de |
| Inyectar / actualizar el token de autenticación HTTP |
| Crear un identificador de sesión con una credencial de un solo uso |
| Liberar recursos (se puede |
| La versión actual del SDK |
OysterTravel — el identificador de sesión para una experiencia, creado por createTravel(ticket:).
Método / Propiedad | Descripción |
|---|---|
| Vista de reproducción (UIKit / SwiftUI) |
| Stream de push de cambio de estado + error / estado actual observable |
| Conectar y reproducir (opcionalmente solicitar la duración máxima de una experiencia de aventura) |
| Instrucción de texto en modo dirección |
| Control del modo Aventura |
| Pausar / reanudar ( |
| Rebobinar (solo en pausa) |
| Finalizar (idempotente, asegúrese de llamarlo) |
| 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 |
Lenguaje | Swift ( |
Concurrencia | Acceso desde el hilo principal (los tipos de entrada están marcados con |
Red | Se requiere acceso a la red pública |
Permisos |
|
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:
- 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. - Credencial de prueba de un solo uso
ticket: Su servidor la obtiene mediante intercambio a través de la plataforma abiertaget-travel-credential(prefijotk_, válida durante 30 minutos, de un solo uso) y se utiliza como argumento decreateTravel(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:
- Al llamar a APIs como
engine.createTravelotravel.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. - Al escuchar el evento
.errordeOysterTravelEvent, 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.apiHostes 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.modeles 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 §8OysterConfig. - Un modelo sirve a un
mode: cada modelo por modo tiene su propia ruta de puerta de enlace, por lo que una sola llamada ainitializesolo sirve mundos de ese únicomode. Si su aplicación ofrece mundos en varios modos, simplemente llame ainitialize()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 requierecleanup()y se conserva el token inyectado; mientras un Travel está en curso, la llamada se ignora, así que invoqueend()primero. Un modelo que no coincida con elmodedel mundo es rechazado por la puerta de enlace conAccessDenied, normalizado a106003. - Cuándo usar: Llamar una vez antes de
createTravel, lo antes posible después del inicio de la aplicación. - Devuelve:
Bool— indica si laconfigque usted proporcionó surtió efecto. Esfalseen dos casos: la configuración no es válida (apiHost/modelen 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/modelno requierecleanup(), y se conserva el token inyectado); es una operación nula con solo una advertencia mientras un Travel está en curso, así que invoqueend()primero. Una configuración no válida deja el entorno de ejecución sin cambios. No utiliceisReadypara determinar si una re-initializetuvo éxito: si fue rechazada, la configuración anterior sigue vigente yisReadypermanecetrue;isReadyresponde 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 ysetHandler(_:)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
ticketde un solo uso. - Parámetro:
ticketes 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 (
throwssincrónico): lanza100001si no se ha ejecutadoinitialize; lanza103004si se llama nuevamente antes de que el Travel anterior haya ejecutadoend()(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 medianteend()primero, luego desmantela el entorno de ejecución, sin dejar operaciones fire-and-forget. Después de la liberación, puede ejecutarinitializenuevamente.
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, useOysterVideoView(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:
eventses el flujo push de cambios de estado + errores;statuses el estado externo actual observable;isEndedes un indicador legible sincrónicamente para el estado terminal. - Cuándo usarlo: Se recomienda comenzar a consumir
eventsantes destart(), para evitar perder los primeros estados. - Nota: Cada acceso a
eventsdevuelve un flujo independiente, compatible con múltiples suscripciones; cancelar la suscripción equivale a finalizar la iteraciónfor await(o destruir laTaskretenida). En SwiftUI, puede observar directamentestatuscon@StateObject/@ObservedObject(los errores siguen llegando a través deevents). Consulte la §7.
start() / start(maxExperienceTimeSec:)
- Propósito: Usar el
ticketcapturado 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 deevents. - 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. Pasarnil(o llamar astart()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 terminalendeda través deevents(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
modedel mundo delticketdebe coincidir con elmodelpasado ainitialize(responsabilidad del llamante): con modelos por modo, cada modelo es su propia ruta de puerta de enlace, ystart()envía elticketa la ruta del modelo actualmente inicializado****. El SDK no verifica ni puede verificar esto por usted de antemano —modese entrega mediante la respuesta a esta misma llamada destart()(OysterStartTravelData.mode); antes de la llamada, el SDK solo posee unticketopaco 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 ainitialize()con el modelo correspondiente (mientras está inactivo, prevalece la configuración más reciente — no se requierecleanup()y se conserva el token; mientras un Travel está en curso, la llamada se ignora, así que ejecuteend()primero). En caso de discrepancia, esta llamada destart()falla en la puerta de enlace; al diagnosticar, verifique primero que elmodelactual y elmodedel mundo delticketpertenezcan 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 afailedy expone105006mediante.errordeevents(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
directingyacting; no soportado poradventure. Use elmodedevuelto porstart()para decidir de antemano si mostrar un botón de pausa.pauserequiere que el estado actual searunning;resumerequierepaused. - 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—actingyadventureno 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
ticketse 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 aend(); 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á enpausedy se reenvía con el primer fotograma tras reanudar arunningmediante 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 §8OysterAdventureCommand; 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
.errordeevents, no mediantethrows): sin experiencia activa103001; llamado en modo dirección103003; canal en tiempo real no listo / envío fallido105004.
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
pauseprimero yresumedespué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:
OysterTraveles unObservableObject; use directamente@StateObject/@ObservedObjectpara observarstatusy controlar la interfaz de usuario; los errores siguen proviniendo deevents. - Imperativo / UIKit: en un
Task, usefor await event in travel.events { ... }yswitchsobre.statusChanged/.error; cancele elTaskretenido 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 |
|---|---|---|
| después de crear, antes de iniciar | — |
| conectando / reconectando (conexión / reconexión interna) | mostrar aviso de conexión / reconexión |
| stream listo, interactivo (reproducción interna) | mostrar imagen y controles |
| pausa aceptada, esperando confirmación del servidor | mostrar "pausando…" |
| pausado (confirmado) | mostrar estado pausado (solo |
| finalizado (finalización activa o finalización del lado del servidor). Terminal | finalizar y cerrar la página |
| 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:
modees externamenteadventure(wander) /directing(historia) /acting(juego de roles); la definición deOysterModeValuees autoritativa. - Nota:
aspectRatioes una cadena abierta (actualmente9:16/16:9, pueden añadirse más). Analícela comowidth:heighty 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 |
|---|---|---|
| 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 |
| Credencial de experiencia ( | Solicitar al servidor que vuelva a emitir la credencial |
| Credencial de experiencia ( | Credencial de un solo uso; volver a emitir |
| 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 |
| Estado del mundo no listo | Espere a que el mundo esté listo antes de comenzar |
| La API solo permite la clave de API principal | No se puede usar una clave temporal para esta API |
| Contenido de entrada rechazado por moderación de contenido; aplica a instrucciones de texto de | Cambie la entrada e inténtelo de nuevo |
| 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) |
| Configuración de capacidad temporalmente no disponible | Reintentar más tarde |
| El recurso no existe (propiedad de mundo/wander o sin artefacto) | Verificar ID / estado |
| La solicitud entra en conflicto con el estado actual del recurso | Verifique el estado de la experiencia |
| Límite de concurrencia alcanzado para esta especificación | Reintente después de que finalice una sesión existente (no confundir con |
| Capacidad disponible insuficiente | Reintentar más tarde |
| Error interno del sistema | Reintentar más tarde / reportarlo |
| 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 |
|---|---|---|---|
| Llamado antes de la inicialización del SDK; también cubre un | No (lanzado sincrónicamente, rechazando esta llamada) | Ejecute |
| Token de autenticación HTTP no inyectado | No | reintentar después de |
| Token de autenticación HTTP no válido / rechazado | No | volver a intercambiar el token y luego reintentar |
| No hay experiencia activa actualmente | No (rechazando esta llamada) |
|
| El estado/versión actual no permite esta operación | No (rechazando esta llamada) | verificar estado de la experiencia / |
| Desajuste de modo (por ejemplo, | No (rechazando esta llamada) | elegir la API correcta por |
| Creación/inicio concurrente de experiencia | No (lanzado sincrónicamente) | serializar llamadas, |
| Error de conexión en tiempo real | Sí | finalizar y reiniciar |
| Tiempo de espera de unión en tiempo real agotado | Sí | finalizar y reiniciar |
| Tiempo de espera agotado esperando el primer fotograma de video | Sí | finalizar y reiniciar |
| 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 |
| Tiempo de espera de callback agotado (predeterminado 30s) | No | reintentar y aumentar |
| Sin stream después de unirse; el SDK finaliza automáticamente la experiencia | Sí | finalizar y reiniciar |
| Error de red local | No | reintentable |
| Error al analizar la respuesta | depende | actualizar el SDK / reportar |
| Código de error no reconocible / código de error de cadena de proxy | No | reintentable |
| Deshabilitado remotamente por el interruptor de funciones del servidor (apagado total o versión demasiado baja; motivo en | Sí | Seguir el motivo en |
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.statusChangeddeevents) 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.