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 ( |
Subprocesamiento | Las API públicas son |
Importación |
|
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) |
|
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 |
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
noneni llamar aflushCommands()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 aend()al salir de la página de experiencia para garantizar que se liberen la conexión en tiempo real y los recursos.OysterTraveles de un solo uso — cree uno nuevo mediantecreateTraveldespué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
.errordeeventsy pasa al estado terminalfailed; 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 desdeaspectRatio(vertical9:16por defecto).
6. Siguientes pasos
- API completa (
pause()/resume()/rewind(toSec:), modelos de datos, códigos de error) → Referencia de la API del SDK para iOS