Todos los productos
Search
Centro de documentos

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

Última actualización: Oct 12, 2026

Esta es la ruta de incorporación más corta para integradores de iOS: desde la inicialización, hasta mostrar la imagen en pantalla, enviar instrucciones de control y finalizar la experiencia. Para obtener las firmas completas de métodos, campos y códigos de error, consulte la Referencia de la API del SDK para iOS.

Para firmas completas de métodos, campos y códigos de error, consulte la Referencia de la API del SDK para iOS.


0. Lo que construirá

Un bucle mínimo de extremo a extremo de "entrar en un mundo → experiencia de video en tiempo real → interactuar → finalizar". Los puntos de entrada en tiempo de ejecución son el global HappyOysterEngine.shared y el identificador de sesión de un solo uso OysterTravel que este crea.

OysterStream.register()
HappyOysterEngine.shared: initialize → updateToken → createTravel
OysterTravel:             videoView / events → start → sendInstruct / sendCommand → end

1. Requisitos

Elemento

Requisito

SO mínimo

iOS 15.0+

Lenguaje

Swift (async/await)

Subprocesamiento

Las API públicas son @MainActor; llámelas en el hilo principal

Importación

import HappyOysterSDK (punto de entrada agregado, ya incluye @_exported Core + World)

NotaLa comunicación en tiempo real (AliRTC) está encapsulada dentro del SDK; los integradores nunca interactúan directamente con la API de RTC.

1.1 Agregar el SDK mediante CocoaPods

El SDK se distribuye como un binario precompilado (xcframework) a través de subespecificaciones de CocoaPods, publicado en el Trunk público de CocoaPods — haga referencia a él directamente por versión, sin necesidad de un archivo podspec local.

# HappyOysterSDK / AliVCSDK_ARTC are both published on the public CocoaPods source.
source 'https://cdn.cocoapods.org/'

platform :ios, '15.0'
use_frameworks!

target 'YourApp' do
  # Aggregate entry point (Core + World); `import HappyOysterSDK` and you're set.
  pod 'HappyOysterSDK'
  # Optional default UI components (video view, control HUD).
  pod 'HappyOysterSDK/UI'
  # Video stream + AliRTC engine adapter (already depends on Stream; no need to declare it separately).
  pod 'HappyOysterSDK/StreamAliRTC'

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

Luego ejecute pod install y abra el .xcworkspace generado (no el .xcodeproj).

NotaAliVCSDK_ARTC es obligatorio siempre que incluya HappyOysterSDK/StreamAliRTC: si falta, el SDK recurre silenciosamente a Loopback — aún se conecta y alcanza el estado running, pero muestra una pantalla negra sin error.

2. Dos tipos de credenciales (entiéndalas primero para evitar problemas)

El SDK no obtiene ni renueva ninguna credencial por sí mismo; usted debe inyectarlas todas:

Credencial

Origen

Propósito

Cómo inyectar

Token de autenticación HTTP

Su aplicación lo obtiene de su propio backend

Autenticación general para llamadas del SDK a la puerta de enlace (de larga duración, requiere renovación)

updateToken(_:); el SDK conserva únicamente el más reciente

Ticket de un solo uso

Emitido por su servidor tras el intercambio mediante la API de credenciales de Travel

Se utiliza para una única experiencia y se invalida inmediatamente después de su uso

Pasado como argumento de createTravel(ticket:)

NotaAmbos no son intercambiables: updateToken es para autenticación general, mientras que ticket es una credencial de unión de un solo uso. Las claves AK / de firma existen únicamente en su servidor y nunca se exponen al cliente.


3. Pasos de integración

Ciclo de vida: registrar el motor de streaming → inicializar → inyectar token → crear la sesión → adjuntar video + suscribirse a eventos → iniciar → interactuar → finalizar.

Paso 1: Registrar el motor de streaming

El único punto de entrada para renderizar video. Llámelo una vez al iniciar la aplicación; las llamadas repetidas son seguras.

import HappyOysterSDK
import HappyOysterStream

OysterStream.register()

Paso 2: Inicializar el SDK

Debe inicializar una vez antes de llamar a cualquier otra API. Para cambiar de puerta de enlace o modelo, simplemente llámelo de nuevo — mientras está inactivo, prevalece la configuración más reciente y se conserva el token inyectado; la llamada solo se ignora mientras un travel está en curso, así que ejecute end() primero.

let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
    apiHost: "[workspace-id].[region].maas.aliyuncs.com",  // Model Studio gateway host
    model: "happyoyster-1.0-adventure"                     // Versioned model name enabled for your account, required
))
// Optional: override the log level / signalling callback timeout
// OysterConfig(apiHost: "…", model: "…", logLevel: .debug, callbackTimeoutMs: 30_000)

// Both apiHost and model are required with no default: Happy Oyster is split into
// per-mode sub-models. See the Happy Oyster model documentation for available names
// and versions; they must match the account and region of your token.

Paso 3: Inyectar el token de autenticación HTTP (push)

Obtenga una API Key temporal de Model Studio desde su propio backend e inyéctela. Vuelva a inyectarla después de que caduque.

let token = await fetchTokenFromYourBackend()
engine.updateToken(token)

Paso 4: Crear el identificador de sesión

Cree un OysterTravel a partir de un ticket de un solo uso. Aún no hay nada conectado, pero la vista de video ya está disponible.

let travel = try engine.createTravel(ticket: ticket)

Paso 5: Adjuntar la vista de video y suscribirse a eventos

El SDK proporciona la vista y el host la coloca. Suscríbase antes start() para no perderse los primeros cambios de estado.

containerView.addSubview(travel.videoView)     // SwiftUI: OysterVideoView(travel:)

let eventTask = Task {
    for await event in travel.events {
        switch event {
        case .statusChanged(let status): render(status)   // running / paused / ended / failed…
        case .error(let error):          handle(error)    // error.code / error.kind — see the API Reference
        }
    }
}

Paso 6: Iniciar la experiencia

Conéctese y comience a reproducir. El SDK mantiene entonces automáticamente la conexión en tiempo real y el sondeo de estado, exponiendo el estado a través de events.

let data = try await travel.start()
// data.encryptedTravelId —— identifier of this experience, used for diagnostics / server reconciliation
// data.encryptedWorldId  —— identifier of the world you entered
// data.mode              —— adventure / directing / acting, determines the interaction UI
// data.aspectRatio       —— "9:16" / "16:9"; set for acting only, use it to pick the player orientation

Notastart(maxExperienceTimeSec:) solo aplica a adventure; directing y acting ignoran el parámetro.

El modo del mundo para este travel debe coincidir con el model que pasó a initialize en el Paso 2. Con modelos por modo (happyoyster-1.0-adventure / -directing / -acting), cada modelo es su propia ruta de aplicación de puerta de enlace, por lo que una sola llamada a initialize sirve únicamente a mundos de un modo; el ticket fue emitido por su servidor bajo la ruta del modo de ese mundo, y start() lo envía a la ruta del model configurado actualmente — una discrepancia provoca un fallo en este paso.

Antes de entrar en un mundo de un modo diferente, llame nuevamente a initialize() con el modelo correspondiente — sin cleanup() y sin volver a ejecutar updateToken():

HappyOysterEngine.shared.initialize(config: OysterConfig(
    apiHost: "…", model: "happyoyster-1.0-acting"))   // the model for this world's mode

Mientras está inactivo (sin ningún travel en curso), prevalece la configuración más reciente y se conserva el token inyectado. El SDK no puede verificar por usted la coincidencia modelo/mundo de antemano: mode solo llega en la respuesta de este paso (data.mode); antes de la llamada, el SDK solo posee un ticket opaco y un nombre de modelo. Las aplicaciones que ofrecen un solo modo no se ven afectadas y se inicializan una sola vez.

NotaMientras un travel está en curso, una llamada repetida a initialize() se ignora con una advertencia — ejecute end() primero.

Paso 7: Interacción en tiempo real (elija uno según el modo)

if data.mode == .adventure {
    // Adventure: direction/view/action control (fire-and-forget; never throws, failures come via events)
    travel.sendCommand(OysterAdventureCommand(translation: .front, interaction: .jump))
} else {
    // Directing and acting: text instructions
    _ = try await travel.sendInstruct(content: "Pan to the castle; the hero starts running")
}

sendCommand limita internamente la frecuencia en un ciclo de última-llamada-gana de 42ms (24FPS), por lo que el host puede llamarlo a alta frecuencia:

  • Las acciones de un solo disparo, como saltar, atacar, agacharse y correr, se envían una sola vez.
  • El movimiento y la rotación de la vista se envían continuamente mientras se mantienen presionados, y simplemente deja de llamarlos al soltarlos.
  • No necesita enviar none ni llamar a flushCommands() al soltar; el SDK nunca genera un comando de detención por sí mismo, y el servidor finaliza la acción una vez que el canal en tiempo real queda inactivo.

Paso 8: Pausar / Reanudar / Rebobinar (dependiente del modo)

_ = try await travel.pause()           // supported by directing and acting
_ = try await travel.resume()
_ = try await travel.rewind(toSec: 10) // directing only

Notaadventure no admite none de estos; acting admite pausa/reanudación pero no admite rebobinado — oculte el punto de entrada de rebobinado en esos modos. Las llamadas no coincidentes son rechazadas localmente por el SDK (103003 / 103002).

Paso 9: Finalizar la experiencia

Desconecta, detiene el sondeo y libera todos los recursos de la sesión; el ticket se consume. Es idempotente — cada ruta de salida debe converger en él.

_ = try? await travel.end()
eventTask.cancel()

NotaLlame a await engine.cleanup() cuando desmonte el SDK o cambie de puertas de enlace.


4. Ejemplo completo (SwiftUI)

import SwiftUI
import HappyOysterSDK
import HappyOysterStream

@main
struct MyApp: App {
    init() {
        OysterStream.register()                                     // Step 1
        HappyOysterEngine.shared.initialize(config: OysterConfig(   // Step 2
            apiHost: "[workspace-id].[region].maas.aliyuncs.com",
            model: "happyoyster-1.0-adventure"
        ))
    }
    var body: some Scene { WindowGroup { TravelScreen() } }
}

struct TravelScreen: View {
    @State private var travel: OysterTravel?
    @State private var eventTask: Task<Void, Never>?

    var body: some View {
        ZStack {
            if let travel {
                OysterVideoView(travel: travel)      // Step 5: SDK provides the view, host places it
                    .ignoresSafeArea()
            } else {
                Color.black.ignoresSafeArea()
            }
        }
        .task { await start() }
        .onDisappear { Task { await end() } }
    }

    @MainActor private func start() async {
        let engine = HappyOysterEngine.shared
        engine.updateToken(await fetchToken())                      // Step 3
        do {
            let ticket = await fetchTicket()                        // issued by your server
            let travel = try engine.createTravel(ticket: ticket)    // Step 4
            self.travel = travel

            eventTask = Task {                                      // Step 5
                for await event in travel.events {
                    switch event {
                    case .statusChanged(let status): print("status: \(status)")
                    case .error(let error):          print("error: \(error.code)")
                    }
                }
            }

            let data = try await travel.start()                     // Step 6
            if data.mode == .adventure {                            // Step 7
                travel.sendCommand(OysterAdventureCommand(translation: .front))
            } else {
                _ = try await travel.sendInstruct(content: "Suddenly it starts to pour")
            }
        } catch let error as OysterSDKError {
            // Handle start failure (see the error code table in the API Reference)
        } catch {}
    }

    @MainActor private func end() async {
        _ = try? await travel?.end()                                // Step 9
        eventTask?.cancel()
        travel = nil
    }
}

5. Mejores prácticas

  • Ciclo de vida: Llame a OysterStream.register() + initialize() lo antes posible al iniciar la aplicación, una vez cada uno globalmente; llame siempre a end() al salir de la página de experiencia para garantizar que se liberen la conexión en tiempo real y los recursos. OysterTravel es de un solo uso — cree uno nuevo mediante createTravel después de que alcance un estado terminal.
  • Renovación de token: Asegúrese de que el token HTTP esté actualizado antes de iniciar una experiencia; cuando reciba una devolución de llamada de error relacionada con la autenticación, obtenga un nuevo token y llame a updateToken (no es fatal, no termina la experiencia).
  • Gravedad del error: Para errores fatales, el SDK termina automáticamente la experiencia actual, los expone a través de .error de events y pasa al estado terminal failed; debe regresar a la pantalla mostrada antes de "iniciar experiencia". Los errores no fatales solo se notifican y se pueden reintentar. Consulte la tabla completa de códigos de error en la Referencia de la API.
  • Adaptación de modo: Los modos directing y acting muestran una entrada de texto (sendInstruct); el modo adventure muestra widgets de control (sendCommand). Solo directing muestra un punto de entrada para rebobinar. Para mundos en modo acting, seleccione la orientación del reproductor desde aspectRatio (vertical 9:16 por defecto).

6. Siguientes pasos