HappyOyster Guia de integração do iOS SDK
Este é o caminho de integração mais curto para integradores iOS: desde a inicialização, passando pela imagem na tela, passando pelo envio de instruções de controle, até o final da experiência. Para obter assinaturas completas de métodos, campos e códigos de erro, consulte iOS SDK API Reference.
Para assinaturas completas de métodos, campos e códigos de erro, consulte a Referência da API do SDK do iOS.
0. O que você construirá
Um loop mínimo de ponta a ponta de "entrar em um mundo → experiência de vídeo em tempo real → interagir → terminar". Os pontos de entrada do tempo de execução são os globais HappyOysterEngine.shared e o identificador de sessão de uso único OysterTravel ele cria.
OysterStream.register()
HappyOysterEngine.shared: initialize → updateToken → createTravel
OysterTravel: videoView / events → start → sendInstruct / sendCommand → end
1. Requisitos
<table><colgroup><col style={{ width: "25%" }} /><col style={{ width: "75%" }} /></colgroup><thead><tr><th> Artigo </th><th> Requisito </th></tr></thead><tbody><tr><td> SO mínimo </td><td> iOS 15.0+ </td></tr><tr><td> Idioma </td><td> Rápido (<code>assíncrono/aguardar</code>) </td></tr><tr><td> Rosqueamento </td><td> APIs públicas são <code>@MainActor</code>; ligue para eles no tópico principal </td></tr><tr><td> Importar </td><td> <code>importar HappyOysterSDK</code> (ponto de entrada agregado, já <code>@_exportado</code> Núcleo + Mundo) </td></tr></tbody></table>
A comunicação em tempo real (AliRTC) é encapsulada dentro do SDK; os integradores nunca tocam diretamente na API RTC.
1.1 Adicione o SDK via CocoaPods
O SDK é distribuído como um binário pré-compilado (xcframework) por meio de subespecificações do CocoaPods, publicado no CocoaPods Trunk público — referencie-o diretamente por versão, sem necessidade de arquivo 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
Então corra pod install e abra o gerado .xcworkspace (não o .xcodeproj).
AliVCSDK_ARTC é obrigatório sempre que você entra HappyOysterSDK/StreamAliRTC: se estiver faltando, o SDK volta silenciosamente para Loopback — ele ainda se conecta e alcança running, mas mostra uma tela preta sem erros.
2. Dois tipos de credenciais (entenda-as primeiro para evitar armadilhas)
O SDK não obtém ou atualiza nenhuma credencial por conta própria; você injeta todos eles:
<table><colgroup><col style={{ width: "20%" }} /><col style={{ width: "25%" }} /><col style={{ width: "35%" }} /><col style={{ width: "20%" }} /></colgroup><thead><tr><th> Credencial </th><th> Fonte </th><th> Objetivo </th><th> Como injetar </th></tr></thead><tbody><tr><td> Token de autenticação HTTP </td><td> Seu aplicativo o busca em seu próprio back-end </td><td> Autenticação geral para chamadas de SDK para o gateway (de longa duração, precisa de renovação) </td><td> <code>atualizarToken(_:)</code>; o SDK mantém apenas o mais recente </td></tr><tr><td> Bilhete único </td><td> Emitido pelo seu servidor após a troca por meio da API de credenciais de viagem </td><td> Usado para uma única experiência, invalidado imediatamente após o uso </td><td> Aprovado como o <code>criarViagem(bilhete:)</code> argumento </td></tr></tbody></table>
Os dois não são intercambiáveis: updateToken é para autenticação geral, enquanto ticket é uma credencial de associação única. As chaves AK/assinatura existem apenas no seu servidor e nunca são expostas ao cliente.
3. Etapas de integração
Ciclo de vida: registrar o mecanismo de stream → inicializar → injetar token → criar a sessão → anexar vídeo + inscrever-se em eventos → iniciar → interagir → finalizar.
Etapa 1: Registre o Stream Engine
O único ponto de entrada para renderização de vídeo. Chame-o uma vez na inicialização do aplicativo; chamadas repetidas são seguras.
import HappyOysterSDK
import HappyOysterStream
OysterStream.register()
Etapa 2: inicializar o SDK
Você deve inicializar uma vez antes de chamar qualquer outra API. Para trocar de gateway ou modelo, basta chamá-lo novamente — enquanto ocioso, a configuração mais recente vence e o token injetado é mantido; a chamada é ignorada apenas enquanto uma viagem está em vôo, então end() primeiro.
let engine = HappyOysterEngine.shared
engine.initialize(config: OysterConfig(
apiHost: "maas.qwencloudapi.com", // QwenCloud 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 of your token.
Etapa 3: injetar o token de autenticação HTTP (push)
Buscar um temporário QwenCloud Chave de API do seu próprio back-end e injete-a. Reinjete depois que expirar.
let token = await fetchTokenFromYourBackend()
engine.updateToken(token)
Etapa 4: Crie o identificador de sessão
Crie um OysterTravel de uso único ticket. Nada está conectado ainda, mas a visualização do vídeo já está disponível.
let travel = try engine.createTravel(ticket: ticket)
Etapa 5: Anexe a visualização do vídeo e inscreva-se nos eventos
O SDK fornece a visualização, o host a coloca. Inscreva-se antes start() para que você não perca as primeiras mudanças de status.
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
}
}
}
Etapa 6: inicie a experiência
Conecte-se e comece a jogar. O SDK então automaticamente mantém a conexão em tempo real e a pesquisa de status, revelando o status por meio 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
start(maxExperienceTimeSec:) só se aplica a adventure; directing e acting ignore o parâmetro.
O modo mundial para esta viagem deve corresponder ao model você passou para initialize na Etapa 2. Com modelos por modo (happyoyster-1.0-adventure / -directing / -acting), cada modelo é sua própria rota de aplicativo de gateway, portanto, um único initialize serve mundos de apenas um modo; o ticket foi emitido pelo seu servidor na rota do modo daquele mundo, e start() envia para a rota do atualmente configurado model — uma incompatibilidade falha nesta etapa.
Antes de entrar em um mundo de modo diferente, ligue initialize() novamente com o modelo correspondente - não cleanup() e sem re-updateToken():
HappyOysterEngine.shared.initialize(config: OysterConfig(
apiHost: "…", model: "happyoyster-1.0-acting")) // the model for this world's mode
Enquanto ocioso (sem viagem durante o vôo), a configuração mais recente vence e o token injetado é mantido. O SDK não pode verificar antecipadamente a correspondência de modelo/mundo para você: mode só chega na resposta desta etapa (data.mode); antes da chamada, o SDK não contém nada além de um opaco ticket e um nome de modelo. Os aplicativos que oferecem um modo único não são afetados e são inicializados uma vez.
Enquanto uma viagem está em andamento, um repetido initialize() a chamada é ignorada com um aviso — end() primeiro.
Etapa 7: Interação em tempo real (escolha uma com base no 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 acelera internamente em um ciclo de últimas vitórias de 42ms (24FPS), para que o host possa chamá-lo em alta frequência:
- Ações únicas, como pular, atacar, agachar e correr, são enviadas uma vez.
- O movimento e a rotação da visualização são enviados continuamente enquanto estão retidos e você simplesmente para de chamar ao liberar.
- Você não precisa enviar
noneou ligueflushCommands()no lançamento; o SDK nunca gera um comando de parada por conta própria e o servidor encerra a ação quando o canal em tempo real fica silencioso.
Etapa 8: Pausar / Retomar / Retroceder (depende do modo)
_ = try await travel.pause() // supported by directing and acting
_ = try await travel.resume()
_ = try await travel.rewind(toSec: 10) // directing only
adventure não suporta nada disso; acting suporta pausa/retomada, mas não retrocede — oculta o ponto de entrada de retrocesso nesses modos. Chamadas incompatíveis são rejeitadas localmente pelo SDK (103003 / 103002).
Etapa 9: Encerre a experiência
Desconecta, interrompe a sondagem e libera todos os recursos da sessão; o ticket é consumido. Idempotente — cada caminho de saída deve ser canalizado para ele.
_ = try? await travel.end()
eventTask.cancel()
Ligar await engine.cleanup() ao derrubar o SDK ou trocar gateways.
4. Exemplo 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: "maas.qwencloudapi.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. Melhores práticas
- Ciclo de vida: Chamada
OysterStream.register()+initialize()o mais cedo possível no lançamento do aplicativo, uma vez em todo o mundo; sempre ligueend()ao sair da página de experiência para garantir que a conexão e os recursos em tempo real sejam liberados.OysterTravelé utilizável — crie um novo viacreateTraveldepois de atingir um estado terminal. - Renovação de token: certifique-se de que o token HTTP esteja atualizado antes de iniciar uma experiência; quando você receber um retorno de chamada de erro relacionado à autenticação, busque um novo token e ligue
updateToken(não fatal, não encerra a experiência). - Gravidade do erro: para erros fatais, o SDK encerra automaticamente a experiência atual e os exibe por meio do
.errordeevents, e se instala nofailedestado terminal; você deve retornar à tela mostrada antes de "iniciar experiência". Erros não fatais são apenas revelados e podem ser repetidos. Consulte a tabela completa de códigos de erro na Referência da API. - Adaptação de modo: os modos de direção e atuação mostram uma entrada de texto (
sendInstruct); o modo aventura mostra widgets de controle (sendCommand). Apenas a direção mostra um ponto de entrada de retrocesso. Para mundos de atuação, escolha a orientação do jogador emaspectRatio(retrato9:16por padrão).
6. Próximas etapas
- API completa (
pause()/resume()/rewind(toSec:), modelos de dados, códigos de erro) → Referência da API do SDK do iOS