QwenCloudBRASIL
Tradução de apoio para consulta. Código e identificadores técnicos preservados. Consultar referência original ↗

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:

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

6. Próximas etapas