HappyOyster Guia de integração do Web SDK
Este documento é destinado a desenvolvedores frontend/full-stack e explica como integrar o HappyOyster Web SDK em produção confiável em seus modos de aventura, direção e atuação.
Para APIs individuais, consulte a Referência da API.
0. Exemplo de integração mínima
Se o seu back-end já retornou um QwenCloud chave API temporária token e uma viagem ticket, o fluxo do SDK de front-end será semelhante a este:
import { HappyOysterEngine, isSdkError } from "@happy-oyster/js-sdk";
const engine = new HappyOysterEngine({
APIHost: "open-platform.example.com",
model: "happyoyster-1.0-adventure", // Required; Adventure model shown as an example
logLevel: "warn",
});
engine.updateToken(token);
const videoElement = document.querySelector("video") as HTMLVideoElement;
const travel = engine.createTravel({
ticket,
videoElement,
maxExperienceTimeSec: 90, // Optional, Adventure only: 60 / 90 / 120
});
const offStatus = travel.on("statusChanged", (status) => {
// Use status to drive loading states, disabled buttons, and player UI.
});
const offError = travel.onError((error) => {
// Usually log, show a toast, and refresh the token if it has expired.
if (isSdkError(error)) console.error(error.code, error.message);
});
try {
await travel.start();
// Enable controls or Directing input with travel.can(action) after the session is running.
} finally {
offStatus();
offError();
await travel.end();
}
Este snippet abrange apenas o lado do SDK. Criando o mundo, esperando até que esteja pronto, solicitando o ticket, e solicitando o QwenCloud chave API temporária token tudo deve acontecer no back-end do seu aplicativo ou por meio dele.
1. Visão geral do fluxo ponta a ponta
Uma experiência completa envolve principalmente os seguintes participantes.
1.1 Participantes
<table><colgroup><col style={{ width: "25%" }} /><col style={{ width: "75%" }} /></colgroup><thead><tr><th> Participante </th><th> Responsabilidade </th></tr></thead><tbody><tr><td> Interface do aplicativo </td><td> interface do usuário, <code><video></code>, orquestração de sessão e chamadas de SDK </td></tr><tr><td> Back-end do aplicativo </td><td> Armazena a chave de API primária, chama APIs mundiais de plataforma aberta em nome do cliente e emite QwenCloud credenciais </td></tr><tr><td> HappyOyster API aberta </td><td> Expõe as APIs exigidas pelo HappyOyster modelo mundial. </td></tr><tr><td> HappyOyster SDK </td><td> O SDK usado pelo front-end do aplicativo. Ele gerencia principalmente sessões de viagem e acesso ao stream de vídeo no cliente. </td></tr></tbody></table>

1.2 Fluxo de experiência
<table><colgroup><col style={{ width: "10%" }} /><col style={{ width: "22%" }} /><col style={{ width: "28%" }} /><col style={{ width: "40%" }} /></colgroup><thead><tr><th> Palco </th><th> Ator </th><th> Ação </th><th> Descrição </th></tr></thead><tbody><tr><td> ① Crie um mundo </td><td> Back-end do aplicativo → HappyOyster API aberta </td><td> <code>POSTAR /openapi/v1/mundos</code> </td><td> Devoluções <code>criptografadoWorldId</code>, <code>estado</code>e <code>primeiroQuadro</code> </td></tr><tr><td> ② Espere até estar pronto </td><td> Back-end do aplicativo → HappyOyster API aberta </td><td> <code>OBTER /openapi/v1/worlds/build-status</code> </td><td> Enquete até <code>estado === pronto</code> (pule esta etapa se o mundo já estiver pronto quando criado) </td></tr><tr><td> ③ Obtenha uma credencial de viagem </td><td> Back-end do aplicativo → HappyOyster API aberta </td><td> <code>POST /openapi/v1/worlds/get-travel-credential</code> </td><td> Retorna uma única vez <code>bilhete</code> para entrada-viagem </td></tr><tr><td> ④ Solicite um token de chave de API temporário </td><td> Back-end do aplicativo → HappyOyster API aberta </td><td> <code>POST /api/v1/tokens?expire_in_seconds=1800</code> </td><td> Retorna uma chave de API temporária <code>ficha</code>. Não defina uma expiração muito curta ou o token poderá expirar antes da conclusão da experiência e exigir atualizações frequentes. </td></tr><tr><td> ⑤ Entregue o token de chave de API temporário e a credencial de viagem </td><td> Frontend do aplicativo → backend do aplicativo → frontend do aplicativo </td><td> Solicite uma chave de API temporária e uma credencial de viagem </td><td> Depois que o frontend recebe <code>ficha</code> e <code>bilhete</code>, ele inicia o fluxo do SDK </td></tr><tr><td> ⑥ Entre na experiência </td><td> Interface do aplicativo → HappyOyster SDK </td><td> Consulte "Subfluxo de inicialização do SDK" abaixo </td><td> <code>novo HappyOysterEngine/updateToken</code> → <code>criarViagem</code> → <code>início()</code> </td></tr></tbody></table>
1.3 Subfluxo de interação do SDK (estágio expandido ⑥)
1.3.1 Iniciar uma viagem

Principais requisitos para pedidos de chamada
<table><colgroup><col style={{ width: "10%" }} /><col style={{ width: "30%" }} /><col style={{ width: "60%" }} /></colgroup><thead><tr><th> Passo </th><th> API </th><th> Descrição </th></tr></thead><tbody><tr><td> 1 </td><td> <code>novo HappyOysterEngine()</code> </td><td> Reutilizar um motor para o mesmo <code>APIHost + modelo</code> </td></tr><tr><td> 2 </td><td> <code>atualizarToken()</code> </td><td> Conclua isso antes <code>início()</code> </td></tr><tr><td> 3 </td><td> <code>criarViagem()</code> </td><td> <code>elemento de vídeo</code> é obrigatório </td></tr><tr><td> 4 </td><td> <code>viajar.em(...)</code> </td><td> Cadastre ouvintes antes <code>início()</code> </td></tr><tr><td> 5 </td><td> <code>aguarde viagem.start()</code> </td><td> Entra <code>correndo</code> depois de um início bem-sucedido </td></tr></tbody></table>
1.3.2 Jogue no modo Aventura

1.3.3 Jogar no modo Direção/Atuação

Use esta tabela como modelo mental rápido para aventura, direção e atuação:
<table><colgroup><col style={{ width: "15%" }} /><col style={{ width: "20%" }} /><col style={{ width: "25%" }} /><col style={{ width: "40%" }} /></colgroup><thead><tr><th> Modo </th><th> Principais APIs </th><th> Uso típico </th><th> Cuidado com </th></tr></thead><tbody><tr><td> Aventura (<code>1</code>) </td><td> <code>enviarComando()</code> </td><td> Controle em tempo real baseado em teclado, joystick e botão </td><td> Enviar somente depois <code>correndo</code>; <code>maxExperienceTimeSec</code> aplica-se apenas a este modo </td></tr><tr><td> Direção (<code>2</code>) </td><td> <code>enviarInstruct()</code>, <code>pausa()</code>, <code>currículo()</code>, <code>retroceder()</code> </td><td> Direção em linguagem natural e controle de reprodução </td><td> Pausar, retomar e retroceder dependem da capacidade da sessão; verificar <code>viajar.can(ação)</code> primeiro </td></tr><tr><td> Atuando (<code>3</code>) </td><td> <code>enviarInstruct()</code>, <code>pausa()</code>, <code>currículo()</code> </td><td> Experiências orientadas para o desempenho e configuração de proporção </td><td> Pausar e retomar dependem da capacidade da sessão; <code>retroceder()</code> está indisponível e <code>lista de scripts</code> não posso usar <code>enviarInstruct()</code> </td></tr></tbody></table>
1.4 Práticas de back-end de aplicativos (resumo)
No back-end, mantenha as seguintes regras simples e rígidas:
- Mantenha a chave de API primária apenas no back-end do aplicativo. O navegador deve receber apenas uma mensagem de curta duração
token(Demonstração:POST /server-api/temp-api-key). - Solicite credenciais de viagem por meio do back-end. Autorize o mundo e o usuário antes de emitir
ticket; não codifique ou armazene em cache a longo prazo no front-end. - <strong>Não emita uma credencial até que o mundo esteja
ready.</strong> Caso contrário, a viagem pode falhar ou proporcionar uma experiência incompleta. - Aponte o back-end do aplicativo e o SDK para o mesmo ambiente de plataforma aberta. O back-end pode usar uma URL OpenAPI completa; o SDK
APIHostdeve ser apenas o host vazio, comoopen-platform.example.com.
2. Conceitos básicos do SDK
No fluxo descrito em §1, o SDK é responsável apenas por “entrar Viagem → jogar → controlar → finalizar”. Existem dois objetos principais:
<table><colgroup><col style={{ width: "30%" }} /><col style={{ width: "70%" }} /></colgroup><thead><tr><th> Objeto </th><th> Objetivo </th></tr></thead><tbody><tr><td> <code>HappyOysterEngine</code> </td><td> Configura <code>HostAPI</code> e <code>modelo</code>, gerencia o QwenCloud token de chave de API temporário (<code>atualizarToken</code>) e cria sessões de viagem </td></tr><tr><td> <code>Viajar</code> </td><td> Representa uma sessão: <code>começar</code> conecta-se ao RTC; <code>enviarComando</code>, <code>enviarInstruir</code>e os métodos de ciclo de vida controlam a sessão </td></tr></tbody></table>
Estados de sessão (statusChanged):
idle ──start()──► prepare ──video playable──► running ⇄ paused ──end()──► idle
Três restrições (explicado mais tarde):
- Cada Motor pode ter apenas uma Viagem ativa por vez.
videoElementdeve ser fornecido acreateTravel.- O QwenCloud o token temporário da chave API e o bilhete de viagem têm finalidades diferentes (consulte as etapas ④–⑤ em §1.2 e §3.2).
3. Inicialização e gerenciamento de token
3.1 Instanciação do mecanismo
Obrigatório
- Configurar
APIHostcomo o host básico da plataforma aberta, semhttps://, um esquema ou um caminho, comoopen-platform.example.com. - Reutilize um
HappyOysterEnginepara o mesmoAPIHost + model. Antes de trocar o host ou modelo, finalize o Travel ativo e utilize um Engine configurado para o novo alvo.
Recomendado
- Usar
logLevel: 'debug'em desenvolvimento e'warn'ou'none'em produção. - Crie o Engine na camada de orquestração da sessão em vez de construir um novo para cada inicialização.
Use esta tabela rápida para os campos de configuração comuns:
<table><colgroup><col style={{ width: "20%" }} /><col style={{ width: "10%" }} /><col style={{ width: "70%" }} /></colgroup><thead><tr><th> Campo </th><th> Obrigatório </th><th> Orientação </th></tr></thead><tbody><tr><td> <code>HostAPI</code> </td><td> Sim </td><td> Passe apenas o host vazio, como <code>plataforma aberta.exemplo.com</code>; não inclua <code>https://</code> ou um caminho </td></tr><tr><td> <code>modelo</code> </td><td> Sim </td><td> Obrigatório, sem padrão; mantenha-o alinhado com o modelo backend usado para criar o mundo e emitir seu ticket. É fixo para o Motor e não é inferido <code>modo</code> ou o bilhete </td></tr><tr><td> <code>ficha</code> </td><td> Não </td><td> Você pode passá-lo no construtor ou configurá-lo posteriormente com <code>atualizarToken(token)</code>; atualizar tokens expirados com <code>atualizarToken</code> também </td></tr><tr><td> <code>nível de registro</code> </td><td> Não </td><td> Usar <code>depurar</code> em desenvolvimento e <code>avisar</code> ou <code>nenhum</code> em produção </td></tr><tr><td> <code>streamReadyTimeout</code> </td><td> Não </td><td> Aumente-o se a configuração da rede ou da renderização estiver lenta; forneça um caminho de nova tentativa após o tempo limite </td></tr></tbody></table>
Evite
- Ligando repetidamente
new HappyOysterEngine()durante a renderização ou em um caminho ativo. - Passar um URL completo, como um com
https://ou chamar APIs de back-end do SDK após omitirAPIHost.
3.2 Modelo de credencial dupla
Correspondente aos estágios ③–⑤ em §1.2:
<table><colgroup><col style={{ width: "20%" }} /><col style={{ width: "25%" }} /><col style={{ width: "25%" }} /><col style={{ width: "30%" }} /></colgroup><thead><tr><th> Credencial </th><th> Fonte </th><th> Como configurá-lo </th><th> Objetivo </th></tr></thead><tbody><tr><td> QwenCloud token de chave de API temporário </td><td> Chave de curta duração emitida pelo back-end do aplicativo </td><td> <code>sdk.updateToken(token)</code> </td><td> Autentica solicitações de SDK para Open Platform </td></tr><tr><td> Bilhete de viagem </td><td> Credencial de viagem solicitada pelo back-end do aplicativo </td><td> <code>createTravel({ ticket })</code> </td><td> Credencial única para uma sessão de viagem </td></tr></tbody></table>
Obrigatório
- Prepare ambas as credenciais antes
travel.start(). - O SDK não persiste ou atualiza automaticamente o token.
Recomendado
- Se o token expirar ou um erro de pulsação for recebido por meio
onError, solicite um novo token por meio do back-end do aplicativo e ligueupdateToken.
Evite
- Misturando as duas credenciais.
- Expor a chave API primária ao frontend do aplicativo.
4. Ciclo de vida da viagem
4.1 Criar e iniciar
Obrigatório
- Ligar
createTravel({ ticket, videoElement });ticketé a credencial de viagem única evideoElementé fornecido neste momento. - Cadastre-se
statusChangedeonErrorantesawait travel.start()para que erros e mudanças de estado sejam sempre observados.
Recomendado
- Mostrar uma interface de usuário de conexão durante
preparepara evitar cliques duplicados. UsartravelInfoReadypara ler os metadados da sessão antecipadamente e usarfirstFrameGeneratedcomo um espaço reservado para o primeiro quadro, quando disponível.
Evite
- Criando uma Viagem antes que o elemento DOM esteja pronto.
4.2 Durante a execução
- Dirija a UI de
statusChanged:preparesignifica esperar pelo vídeo, enquantorunningsignifica que a interação está disponível. - Observe os casos de uso e pré-condições para
pause,resumeerewind(eles dependem da capacidade da sessão;rewindtambém exige que a sessão sejapausede o fluxo parou).
4.3 Finalizar e recriar
Obrigatório
- Termine a sessão com
await travel.end(). - Certifique-se de que a viagem anterior terminou antes de criar uma nova.
- Ligar
travel.end()durante a limpeza quando a página é descarregada ou a rota muda.
Recomendado
- Após uma falha, use "finalizar → remover ouvintes → tentar novamente do estágio ③ ou ⑤ em §1.2."
- Solicite uma nova credencial de viagem ao tentar novamente se a credencial for de uso único.
Evite
- Chamando
createTraveluma segunda vez sem terminar a primeira viagem; isso é lançado de forma síncrona. - Sair da página sem limpeza, o que vaza recursos do RTC.
5. Elemento de vídeo e políticas do navegador
Obrigatório
- Definir
playsInlineligado<video>(obrigatório no iOS). - Entenda como as políticas de reprodução automática do navegador afetam
autoPlayemuted.
Recomendado
- Durante
prepare, mostre a imagem do primeiro quadro com uma sobreposição; escondê-lo depois que a sessão entrarrunning. - Configurar
streamReadyTimeoutconforme necessário (padrão: 15.000 ms) e forneça uma ação de nova tentativa após um tempo limite. - Reserve uma proporção fixa para o contêiner do player.
Evite
- Passar uma referência de elemento de vídeo antes que o elemento seja anexado ao DOM.
- Deixar a UI interativa enquanto a sessão está
pausedou o fluxo está desconectado.
6. Assinaturas de eventos e tratamento de erros
6.1 Assinaturas de eventos
Obrigatório
- Os eventos pertencem a
Travel, nãoHappyOysterEngine. - Salve as funções de cancelamento retornadas por
travel.on(...)etravel.onError(...).
Recomendado
- Registre e limpe os ouvintes em um só lugar. Remova ouvintes antigos antes de mudar de sessão do Travel.
<table><colgroup><col style={{ width: "25%" }} /><col style={{ width: "75%" }} /></colgroup><thead><tr><th> Evento </th><th> Objetivo </th></tr></thead><tbody><tr><td> <code>status alterado</code> </td><td> Exibir status, detectar prontidão de vídeo (<code>correndo</code>) e ativar ou desativar botões </td></tr><tr><td> <code>travelInfoReady</code> </td><td> Leia <code>ID de viagem criptografado</code>, modo, primeiro quadro, duração da experiência e proporção de atuação antes da conexão do RTC; <code>viajar.getInfo()</code> retorna os mesmos dados </td></tr><tr><td> <code>primeiroFrameGenerated</code> </td><td> Quando existe um primeiro quadro não vazio, segue-se <code>travelInfoReady</code>; use-o como um espaço reservado durante <code>preparar</code> </td></tr><tr><td> <code>erro</code> (através <code>onError</code>) </td><td> Registro, notificações do sistema e acionamento de atualização de token </td></tr></tbody></table>
6.2 Camadas de erro
- Falhas de inicialização:
travel.start()rejeita (enter-travel/RTC/timeout). - Erros de tempo de execução: relatado através
travel.onError(Erros de RTC, falhas de autenticação de pulsação e assim por diante). - Erros de back-end de aplicativo e plataforma aberta nos estágios ①–④ de §1.2 são tratados pela camada de aplicativo e não entram no SDK.
Evite
- Capturando apenas
start()sem ouvironError. - Registrar ouvintes repetidamente sem cancelar a inscrição.
7. Envio de instruções: comando e instrução
7.1 sendCommand (controle em tempo real)
sendCommand() fornece controle em tempo real no modo Aventura (1). Ele envia comandos através do RTC DataChannel e está disponível apenas enquanto a sessão está running.
O SDK envia no máximo um comando aproximadamente a cada 42 ms (cerca de 24 FPS). Se tiver decorrido um ciclo desde o envio anterior, ele envia o novo comando imediatamente. Se sendCommand() é chamado várias vezes no mesmo ciclo, envia apenas o comando completo da última chamada. translation, rotatione interaction são dimensões de controle independentes. Os campos omitidos são enviados como None; o SDK não mescla campos entre chamadas, repete periodicamente o comando anterior ou envia automaticamente None.
Ações pontuais
Ligar sendCommand() uma vez para ações únicas, como pular ou atacar:
await travel.sendCommand({ interaction: "Jump" });
Ações contínuas
Continue ligando sendCommand() enquanto um controle de movimento ou rotação de visualização contínua permanece pressionado. O aplicativo frontend não precisa limitar as chamadas para 42 ms; o SDK reúne chamadas de alta frequência em uma taxa de saída de cerca de 24 FPS. No lançamento, envie explicitamente None para redefinir o controle:
// Keep calling while the control is pressed
void travel.sendCommand({ translation: "Front" });
// Stop moving on release
void travel.sendCommand({ translation: "None" });
Evite
- Envio de muitos comandos durante
prepare. - Usando
sendCommandpara instruções de direção em linguagem natural.
7.2 sendInstruct (Instruções de direção)
Obrigatório
- uma sessão de direção (
2) ou uma sessão de atuação (3) isso não éscriptlist; ligarawait travel.sendInstruct({ content }).
Recomendado
- Verifique isso
contentnão está vazio antes do envio e evita envios duplicados. - Fornecer feedback do usuário com base em campos retornados, como
accepted.
8. Solução de problemas
<table><colgroup><col style={{ width: "25%" }} /><col style={{ width: "35%" }} /><col style={{ width: "40%" }} /></colgroup><thead><tr><th> Sintoma </th><th> Causa comum </th><th> O que fazer </th></tr></thead><tbody><tr><td> A inicialização do SDK falha com um erro de argumento </td><td> <code>HostAPI</code> foi passado como um URL completo, por exemplo com <code>https://</code> ou um caminho </td><td> Passe apenas o host vazio, como <code>plataforma aberta.exemplo.com</code> </td></tr><tr><td> <code>viajar.start()</code> falha porque o ticket é inválido ou já foi usado </td><td> <code>bilhete</code> é de uso único; pode expirar, ser reutilizado ou emitido antes que o mundo esteja pronto </td><td> Solicite um novo <code>bilhete</code> do back-end; certifique-se de que o mundo esteja pronto antes de entrar </td></tr><tr><td> <code>onError</code> relata um erro de autenticação após a execução da sessão </td><td> O QwenCloud chave API temporária <code>ficha</code> expirou </td><td> Peça ao seu back-end um novo token e ligue <code>motor.updateToken(novoToken)</code> </td></tr><tr><td> O player continua carregando ou <code>início()</code> expira </td><td> O fluxo remoto não está pronto, a rede está lenta ou <code>streamReadyTimeout</code> é muito curto </td><td> Forneça uma ação de nova tentativa; aumentar <code>streamReadyTimeout</code> se necessário; verifique os logs de back-end e RTC </td></tr><tr><td> Um segundo <code>criarViagem()</code> a chamada falha porque já existe uma viagem ativa </td><td> A viagem anterior não terminou </td><td> Ligar <code>aguarde viagem.end()</code> e limpe os ouvintes ao sair, tentar novamente ou trocar de sessão </td></tr><tr><td> Controles ou instruções não fazem nada </td><td> A sessão não é <code>correndo</code>, ou a API errada é usada para o modo Aventura, Direção ou Atuação atual </td><td> Disponibilidade do botão Drive de <code>status alterado</code>; ligar <code>viajar.can(ação)</code> antes de ações sensíveis ao modo </td></tr></tbody></table>
Ao depurar, registre três coisas juntas: SDK ErrorCode, atual TravelStatuse logs de solicitação de back-end. Isso geralmente informa se o problema está relacionado à credencial, ao estado, à rede ou à renderização.