HappyOyster Guia de integração do Android SDK
Ao integrar o HappyOyster Android SDK, seu aplicativo pode entrar em mundos gerados por IA em tempo real, proporcionando uma experiência de vídeo interativa em tempo real em três modos: Aventura (exploração do mundo), Direção (direção em tempo real) e Atuação (encenação).
Happy Oyster é um produto de exploração mundial de IA. Ao integrar o Happy Oyster Android SDK, seu aplicativo pode entrar em "mundos" gerados pela IA em tempo real, proporcionando uma experiência de vídeo interativa em tempo real em aventura, dirigir, ou Atuando.
Este documento é para desenvolvedores Android do lado da integração. Abrange instalação, autenticação, fluxo de integração completo, tratamento de eventos e práticas recomendadas, explicando como integrar de forma confiável. Para APIs específicas (assinaturas, parâmetros, valores de retorno, códigos de erro, modelos de dados), consulte a Referência da API do SDK Android do Happy Oyster.
1. O que você pode fazer
-
Iniciar uma experiência (viagem): Entre em um mundo pronto com uma credencial única; o SDK estabelece automaticamente a conexão de vídeo em tempo real.
-
Reprodução em tempo real: o SDK retorna uma visualização de vídeo que você monta em seu layout para reproduzir os recursos visuais gerados pela IA em tempo real.
-
Interação em tempo real:
- Modo de direção (direção): Envie instruções de texto para conduzir o enredo.
- Atuação: Envie também instruções de texto; pausa/retomada estão disponíveis; fazer não rebobinar e fazer não usar
sendCommand. Use oaspectRatioda resposta de junção para definir a orientação do jogador (veja §13 Adaptação de Modo). - Modo aventura (aventura): Envie comandos de controle de direção/ponto de vista/ação para interagir com o mundo.
-
Controle de processo: Pausa/retomar (direção e atuação), retroceder (somente direção), fim (todos os três modos).
-
Retornos de chamada de status e erro: Perceba o status da experiência e as exceções em tempo real por meio de ouvintes de eventos.
O SDK não é responsável pela criação e gerenciamento de mundos, nem expõe diretamente detalhes de comunicação de baixo nível em tempo real – eles são gerenciados pelo seu servidor ou internamente pelo SDK. Você só precisa se concentrar em “iniciar a experiência → jogar → interagir → terminar”.
2. Instalação
O Happy Oyster SDK (cn.happyoyster:opensdk) é publicado em Maven Central; seu mecanismo de comunicação em tempo real subjacente (Alibaba Cloud ARTC) é publicado em Alibaba Cloud Maven. Ambos os repositórios devem ser declarados.
Requisitos ambientais
<table><colgroup><col style={{ width: "30%" }} /><col style={{ width: "70%" }} /></colgroup><thead><tr><th> Artigo </th><th> Requisito </th></tr></thead><tbody><tr><td> minSdk </td><td> 24 (Android 7.0) e superior </td></tr><tr><td> compileSdk </td><td> 36 </td></tr><tr><td> JDK </td><td> Destino de bytecode JDK 11 (conjunto de ferramentas de host: JDK 11 ou superior recomendado) </td></tr><tr><td> Idioma </td><td> Kotlin (co-rotina <code>suspender</code> API) </td></tr><tr><td> ABI </td><td> <code>arm64-v8a</code> / <code>armeabi-v7a</code> (o mecanismo de comunicação em tempo real contém bibliotecas nativas) </td></tr><tr><td> Rede </td><td> É necessário acesso público à Internet </td></tr></tbody></table>
Adicione os repositórios na raiz do projeto settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral() // Happy Oyster SDK (cn.happyoyster:opensdk)
maven("https://maven.aliyun.com/repository/public") // real-time communication engine (Alibaba Cloud ARTC)
}
}
Encontre o último lançamento publicado em Maven Central, substitua <version> abaixo com o número exato da versão e adicione a dependência no módulo build.gradle.kts:
dependencies {
implementation("cn.happyoyster:opensdk:<version>")
}
Fixe a dependência em uma versão exata. Revise as notas de versão antes de atualizar e recompile o código do host após a atualização.
O SDK é publicado como um AAR fino e não incorpora dependências de terceiros; dependências transitivas, como o mecanismo de comunicação em tempo real, são extraídas automaticamente dos repositórios acima durante a resolução, de modo que o O repositório Alibaba Cloud Maven é indispensável—sua ausência causará com.aliyun.aio:AliVCSDK_ARTC falhar na resolução.
Permissões
A própria biblioteca SDK declara apenas INTERNET. O mecanismo de comunicação em tempo real mescla automaticamente um pequeno número de permissões do tipo rede/Bluetooth/configurações de áudio; o manifesto da biblioteca faz não inclua permissões de microfone ou câmera. O stream de vídeo é reproduzido somente por assinatura e o SDK não envia áudio real para o lado remoto.
O anfitrião deve declarar por conta própria: Quando seu targetSdk é 33 ou superior, você deve declarar a permissão de notificação no host AndroidManifest.xml (o mecanismo de comunicação em tempo real inclui um serviço em primeiro plano):
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<strong>Modo aventura (sendCommand) — é recomendado declarar a permissão do microfone</strong>: Os comandos de controle em tempo real do modo aventura utilizam um uplink transportado por um fluxo de áudio silencioso que estabeleça a “identidade do editor” local; o SDK não grava ou carrega seu áudio real. RECORD_AUDIO é não é um pré-requisito difícil para o DataChannel- mesmo sem ele, o fluxo silencioso ainda permanece como editor e o uplink está normalmente disponível. Ainda assim, para estabilidade entre dispositivos, recomendar que, se seu aplicativo usa sendCommand, você declara a permissão no host AndroidManifest.xml e solicite-o em tempo de execução antes de ligar.
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<strong>Sobre o código de erro 105004</strong>: 105004 significa que o canal em tempo real não está pronto/um envio falhou (por exemplo, uma interrupção do DataChannel ou uma anomalia no canal em tempo real). É não é uma consequência necessária de uma permissão ausente—não é acionado diretamente por RECORD_AUDIO sendo não concedido. Modo de direção (sendInstruct) e a reprodução de vídeo não são afetadas. Se o seu aplicativo não usa o modo aventura, essa permissão não é necessária.
Para cortar permissões mescladas, use tools:node="remove".
3. Modelo de autenticação
O SDK não obtém nem atualiza tokens, mantendo-o leve. A autenticação tem duas camadas:
- QwenCloud chave de API do gateway: injetado como um token de portador pelo seu aplicativo via
updateToken(token). O SDK mantém apenas o mais recente; ele não persiste nem o atualiza e, após a alteração da chave, você o injeta novamente. O gateway exige que todas as solicitações do SDK, incluindostartTravel, use este portador. Quando a chave do portador expira ou é inválida, o SDK lançaSDKError(101002); neste caso você deve obter novamente e reinjetar a Chave do Portador (updateToken) em vez de trocar por um novo bilhete. Durante o desenvolvimento/demonstração, você pode usar seu principal QwenCloud Chave de API diretamente como portador (updateToken) para fazer o fluxo funcionar. Em produção, sempre mude para um token de curta duração cunhado pelo seu servidor e nunca agrupe uma chave de API de longa duração em um aplicativo distribuído. - <strong>Credencial de experiência única
ticket</strong>: Trocado pelo seu servidor através da plataforma aberta e entregue ao cliente; usado para um únicostartTravelapenas. Torna-se inválido após o término da experiência (normal ou anormal) e não pode ser reutilizado. Erros de credenciais no nível do ticket são identificados por códigos de servidor de seis dígitos (por exemplo,401010= bilhete inválido/expirado,401011= ticket já usado), que o SDK passa literalmente.
Seu servidor é responsável por criar/gerenciar o Mundo, trocar pelo bilhete de viagem e entregá-lo ao cliente; o Android SDK consome apenas tokens e tickets e não fornece interface de gerenciamento mundial. Durante a inicialização, você deve passar o código da sua conta QwenCloud Anfitrião de API através de SDKConfig.apiHost (da forma maas.qwencloudapi.com, copiado do campo "API Host" na página API Key do QwenCloud console) e passe o necessário e sem padrão SDKConfig.model (happyoyster-1.0-directing / happyoyster-1.0-acting / happyoyster-1.0-adventure, correspondendo à entrada Open API). O SDK completa o URL da solicitação como https://{apiHost}/api/v2/apps/{model}/openapi/v1/{endpoint}, então você não monta você mesmo.
Omitindo model ao construir SDKConfig é um erro em tempo de compilação. Se model está em branco, initialize lança sincronizadamente SDKError(100002) (raw = "SDKConfig.model must not be blank"), não cria ou substitui nenhum tempo de execução e não faz nenhuma solicitação de rede. O host da API, o modelo e a chave de API injetada devem corresponder à conta necessária e à autorização do modelo, caso contrário, o gateway normalmente retornará AccessDenied (em tempo de execução, isso é lançado como SDKError(106003), com o original AccessDenied corpo disponível em SDKError.raw; veja o 106003 linha da tabela de códigos de erro de referência da API para obter uma lista de verificação de solução de problemas). Por segurança, recomendamos fortemente que o cliente injete um token de curta duração cunhado pelo seu servidor como portador, em vez de agrupar uma chave de API de longa duração no aplicativo ou enviá-la para um repositório de código.
4. Início rápido
import cn.happyoyster.opensdk.* // All entry-point classes live in this package: HappyOyster, SDKConfig, TravelStatusValue, ModeValue, SDKError, etc.
// Track the current experience status and this Travel's metadata; use them to decide whether interaction can be sent.
@Volatile private var currentStatus: TravelStatusValue? = null
@Volatile private var currentTravel: StartTravelData? = null
// 1) Initialize (recommended in Application.onCreate)
// apiHost is required: your account's QwenCloud API Host (copied from the API Key page in the QwenCloud console)
// model is required with no default: the complete model name and version; refer to the official QwenCloud HappyOyster model documentation for available values
HappyOyster.initialize(
applicationContext,
SDKConfig(
apiHost = "maas.qwencloudapi.com",
model = "happyoyster-1.0",
),
)
// 2) Inject the QwenCloud gateway API Key (as the Bearer token)
HappyOyster.updateToken(qwenApiKey)
// 3) Listen to SDK events: use onStatusChanged to drive the host state machine and gate interaction capabilities
HappyOyster.addListener(object : HappyOysterListener {
override fun onStatusChanged(status: TravelStatusValue) {
currentStatus = status
when (status) {
// Interaction is allowed only in running; adventure-mode sendCommand is valid only in running.
TravelStatusValue.Running -> markInteractionAllowed()
// Paused is the signal that pauseTravel has actually taken effect (asynchronous, see below); sendInstruct is still allowed here.
TravelStatusValue.Paused -> markTravelPaused()
// Terminal states: the SDK has already ended and released resources; clean up the host-side experience state.
TravelStatusValue.Completed, TravelStatusValue.Failed -> clearActiveTravel()
// init / pending: not yet ready, interaction unavailable.
else -> markInteractionBlocked()
}
}
override fun onError(error: SDKError) {
// Unified error callback; see the error-codes section of the API Reference
}
})
// 4) Start the experience (the ticket is delivered by your server)
lifecycleScope.launch {
try {
// Only one concurrent Travel is allowed per App at a time (alirtc limitation: even with a different ticket
// you cannot start a second Travel concurrently within the same App). Calling again while an experience is in progress throws
// SDKError(103004), whose raw carries only a redacted summary of the ticket currently playing, never the full ticket.
val travel: StartTravelData = HappyOyster.startTravel(ticket)
currentTravel = travel
// Inspect the returned Travel metadata first, then decide how to interact:
// travel.mode —— directing / adventure / acting
// travel.creationModel —— Simple / ScriptList
// travel.aspectRatio —— Acting only; use to set player orientation
val canSendInstruct = (travel.mode == ModeValue.Directing || travel.mode == ModeValue.Acting) &&
travel.creationModel != CreationModelValue.ScriptList
// Note: calling sendInstruct in ScriptList mode throws SDKError(103002); script content is not managed through this SDK.
// 5) Mount the video View (the SDK returns the view; you add it to the layout)
// Acting: size the container from travel.aspectRatio first, then attachVideo() (see §13 Mode Adaptation) —
// the remote view is bound clip-to-fill, so a mismatched container orientation crops the picture.
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// 6) In-run interaction — you must wait for running before sending:
// - sendCommand (adventure): valid only in running; calling in init/pending/paused returns 103002.
// - sendInstruct (directing / Acting): valid in running or paused; calling in init/pending returns 103002.
if (canSendInstruct && currentStatus == TravelStatusValue.Running) {
HappyOyster.sendInstruct("It suddenly starts to rain heavily")
}
} catch (e: SDKError) {
// Handle start failure (e.g., 103004: an experience is already playing)
}
}
// 7) Pause / Resume (directing and Acting; rewind is directing only)
// pauseTravel is asynchronous: the method's return only means "accepted"; the actual pause is determined by onStatusChanged(paused).
// Be sure to wait for the paused callback before allowing resumeTravel.
lifecycleScope.launch {
if ((currentTravel?.mode == ModeValue.Directing || currentTravel?.mode == ModeValue.Acting) &&
currentStatus == TravelStatusValue.Running
) {
HappyOyster.pauseTravel() // accepted; the host marks pausing and waits for the status callback
// …after receiving onStatusChanged(Paused)…
}
}
lifecycleScope.launch {
if (currentStatus == TravelStatusValue.Paused) {
HappyOyster.resumeTravel() // call only after paused
}
}
// 8) End the experience (the SDK automatically disconnects the real-time connection and releases resources)
lifecycleScope.launch { HappyOyster.endTravel() }
5. Assinatura de eventos e tratamento de erros
Usar onStatusChanged para conduzir a máquina de estado host e para controlar os recursos de interação; usar onError para receber erros de tempo de execução uniformemente. Para obter a interface do evento e os códigos de erro completos, consulte a Referência da API do SDK Android do Happy Oyster.
Deve
- Registre o ouvinte imediatamente após
HappyOyster.initialize(...)retorna com sucesso;addListener/removeListenerdeve ser chamado depoisinitialize(chamá-los antes da inicialização lançarSDKError(100001)). - Portão ligado
onStatusChanged(Running)—chamadas de interação são permitidas somente apósrunning; modo aventurasendCommandé válido apenas emrunning. - Alça
onErrortambém; não apenas peguestartTravel. Erros fatais também encerram a experiência (consulte a seção de códigos de erro da Referência da API).
Recomendado
- Ligar
removeListenerem um ponto apropriado do ciclo de vida (comoonDestroy) para evitar vazamentos de memória.
Evite
- Registrar ouvintes repetidamente sem chamar removeListener.
6. Instruções de envio: sendInstruct e sendCommand
sendInstruct (direção/atuação)
sendInstruct é usado na direção e atuação para enviar instruções de texto que conduzem a imagem; ele pode ser chamado tanto no running ou paused estado. No estado pausado, o SDK não retoma automaticamente – o host decide se deseja chamar resumeTravel primeiro e depois envie a instrução (para detalhes do contrato, como limitação e validação de estado, consulte a Referência da API). Um mundo de atuação creationModel é sempre Simple, portanto, a restrição ScriptList se aplica apenas ao direcionamento.
sendCommand (modo aventura)
sendCommand é usado no modo aventura (aventura) para enviar comandos de controle de direção/ponto de vista/ação; é válido apenas em running. O SDK possui um acelerador de últimas vitórias integrado de 42 ms (24 fps), para que o host possa chamá-lo na taxa de quadros do jogo e o SDK se funde automaticamente; nenhuma limitação de taxa manual é necessária. <strong>Ligue uma vez para uma ação única. Para uma ação retida, continue chamando cada quadro enquanto estiver retido e envie explicitamente um None redefinir quando liberado</strong> (para obter o contrato detalhado de limitação e detecção de falhas, consulte a Referência da API).
Prática recomendada: na interface de interação do modo aventura do host, forneça três pontos de entrada de comando independentes (direção do movimento + direção do ponto de vista + interação da ação), mantenha o valor atualmente mantido para cada grupo e envie um instantâneo completo de três campos em cada chamada. Usar "None" somente para dimensões inativas. Quando entradas de dimensões diferentes são mantidas simultaneamente, preserve e envie todos os valores ativos em vez de redefinir as outras dimensões para "None".
7. Pausar / Retomar / Retroceder
Modos aplicáveis: pausar/resumir aplica-se a dirigir e Atuandoe se comportam de forma idêntica em ambos (incluindo a barreira de 3 segundos abaixo); retroceder é direcionar apenas. Chamando pauseTravel / resumeTravel / rewindTravel no modo aventura é rejeitado pelo SDK com 103003. Além disso, o version relatado pelo servidor deve ser o token v2 que seu modo requer (storyV2 para dirigir, actingV2 para Atuação); caso contrário, pausar/retomar retornar 103002.
A pausa é assíncrona: O retorno do pauseTravel método significa apenas que foi aceito; a pausa real é determinada por onStatusChanged(Paused). Entre "chamar pauseTravel" e "recebendo o Paused retorno de chamada", o host pode marcar seu estado de experiência local como pausing; só depois de receber Paused você deve permitir chamadas resumeTravel ou o dependente do estado pausado rewindTravel.
Pausa interna do SDK→reabrir barreira: uma vez Paused é recebido, o host pode ligar resumeTravel / rewindTravel de acordo com o contrato. Se a chamada cair dentro da janela de liquidação de 3 segundos após a confirmação da pausa, o método de suspensão do SDK aguardará o tempo restante antes de enviar a solicitação que reabre a sala em tempo real. O host não precisa de um atraso extra de pausa→retomada e nenhum atraso é adicionado depois que 3 segundos tiverem decorrido naturalmente.
Tempo de espera da chamada do host (recomendado): É recomendado que o host adie a emissão de outro pauseTravel dentro de 3 s depois resumeTravel retorna com sucesso, para evitar alternar com muita frequência. O próprio SDK não impõe esse resfriamento.
Retroceder: rewindTravel está disponível apenas no paused estado; após retroceder, o servidor retoma automaticamente e o SDK reconecta automaticamente o RTC, sem necessidade de intervenção do host. Sequência típica: pause → wait:paused → rewindTravel(sec). Os segundos de retrocesso rewindToSec deve ser múltiplo de 4 (por exemplo, 4, 8, 12); não múltiplos são definidos pelo servidor (por exemplo, 7 → 4), e os segundos efetivos reais são determinados pelo retornado resumedAtSec. Retroceder é apenas dirigir: ligando rewindTravel no modo Atuação ou aventura é rejeitado pelo SDK localmente com 103003, sem emitir nenhuma solicitação HTTP; o anfitrião deve esconder a entrada de retrocesso em vez de apenas esmaecer o botão.
Para obter detalhes do contrato, como semântica assíncrona e espera de 3x novas tentativas (resistência de currículoTravel de 1 s/2 s/3 s), consulte a Referência da API do SDK Android do Happy Oyster.
8. Registro
Etiqueta Logcat do SDK: HappyOysterSDK. O SDK fornece dois caminhos de saída de log independentes:
- Logcat integrado (desativado por padrão): o SDK grava no Logcat somente quando
SDKConfig.logcatEnabled = true(padrãofalse, totalmente silencioso).SDKConfig.logLevelfiltra seu nível mínimo; o padrãoINFOjá carrega as âncoras do ciclo de vida necessárias para reconstruir uma sessão (inicialização, início da viagem/transições de status/fim, junção RTC/primeiro quadro). Solte paraWARNapenas para erros, ou aumentar paraDEBUG/VERBOSEpara um diagnóstico mais profundo.logLevelnão afetalogHandler. - <strong>Retorno de chamada do host
logHandler(recomendado)</strong>: recebe cadaLogRecorddo SDK (o firehose completo), totalmente independente delogLevel/logcatEnablede pode encaminhar para o próprio sistema de registro do host (Logcat, arquivos, plataformas de travamento, etc.). O retorno de chamada deve ser rápido e sem bloqueio e não deve retornar ao SDK; as exceções lançadas são capturadas silenciosamente;LogRecord.messageé redigido e não contém nenhum token do portador,ticketou token RTC em texto simples.
HappyOyster.initialize(
context,
SDKConfig(
apiHost = "maas.qwencloudapi.com", // required: your account's QwenCloud API Host
model = "happyoyster-1.0", // required with no default: complete model name and version
logcatEnabled = true, // enable built-in Logcat (default false)
logLevel = LogLevel.DEBUG, // affects only the built-in Logcat
logHandler = { record -> // optional: forward to the host's own Logcat tag
android.util.Log.d("MyApp/SDK", record.message, record.throwable)
},
),
)
adb logcat -s HappyOysterSDK # output only when built-in Logcat is enabled
adb logcat -s HappyOysterSDK MyApp # also view the host App logs (replace MyApp with your tag)
Correlação de sessão e solicitação: o travelId (i.e. encryptedTravelId) nos logs do SDK é emitido em cheio e é a chave da sessão enviada ao servidor — inclua-a ao investigar uma sessão ou preencher um ticket para alinhar os logs do cliente e do servidor. Cada linha de resposta HTTP também carrega o nome do servidor requestId (o reqId= campo, mostrado como - quando ausente) para identificar uma única solicitação; esse ID aparece apenas nos logs e nunca aparece em nenhum valor de retorno público.
9. Ciclo de vida e memória
- Ligar
initializeemApplication.onCreate, uma vez globalmente. - Reinicialização ociosa (por exemplo, para mudar o host da API ou
model) fecha o tempo de execução ocioso anterior e redefine os ouvintes registrados e o estado do feature-gate; registre novamente os ouvintes depois que ele retornar. A reinicialização é <strong>rejeitado com103004</strong> enquanto uma viagem está começando, ativa ou terminando: sempre aguardeendTravel()primeiro. O SDK nunca descarta uma viagem ativa como efeito colateral deinitialize. - A experiência está vinculada ao ciclo de vida do host: ligue
endTravelnoonDestroydoActivity/Fragment(ou o ViewModelonCleared) para garantir que a conexão em tempo real e os recursos sejam liberados. - Remova a View retornada por
attachVideo()do layout no final (container.removeAllViews()) e ligueremoveListener. - O SDK contém apenas o contexto da aplicação; da mesma forma, não passe uma atividade para o SDK.
10. Corrotinas e Threading
- Os métodos de negócios são
suspend; ligue para eleslifecycleScope/viewModelScopede qualquer contexto de rotina. O SDK coordena o estado e o RTC funciona em seu despachante principal enquanto o HTTP permanece fora do thread principal. - Se a corrotina do chamador for cancelada, o SDK cancelará a solicitação HTTP em andamento da chamada, se houver, e se propagará
CancellationExceptioninalterado em vez de convertê-lo paraSDKError; não o pegue ou engula como uma falha operacional. O cancelamento não reverte uma solicitação que o servidor já aceitou. - Ligar
attachVideo()esendCommand()no tópico principal; uma chamada fora da principal é lançada de forma síncronaIllegalStateException.
11. Gerenciamento de tokens
- A Bearer API Key tem prazo de validade limitado; é recomendável garantir que o token esteja atualizado antes de entrar em uma experiência. Ao receber
onError(101002)(token expirou), obtenha novamente a Chave do Portador e ligueupdateToken—não há necessidade de troca por um novo ingresso.
12. Recuperação de erros
- Para erros fatais: limpe o estado atual da experiência (incluindo a remoção da visualização do vídeo), avise o usuário e permita a reinicialização.
- Para instabilidade de rede (
106001) e anomalias temporárias de serviço upstream (106003): um número limitado de novas tentativas pode ser realizado. - Um síncrono
100002dos meios de inicializaçãomodelestá em branco; forneça o nome completo do modelo e a versão e inicialize novamente. Se um não-brancomodeltem o nome ou a versão errada, foi descontinuado, ainda não foi publicado ou não está autorizado, o gateway normalmente retorna AccessDenied, mapeado para106003; usarSDKError.rawe verifique se omodel, Host de API, Chave de API, correspondência de conta.
(Para a classificação fatal/não fatal, consulte a seção de códigos de erro da Referência da API do SDK Android do Happy Oyster.)
13. Adaptação de modo
- Use o
moderetornado porstartTravelpara decidir quais capacidades de interação expor: direção e atuação use a instrução de textosendInstruct(Atuar também usaaspectRatiopara orientação do jogador e oculta retrocesso); modo aventura usa o comando de controlesendCommand. - No modo aventura você pode usar a sobrecarga
startTravel(ticket, maxExperienceTimeSec)para limitar a duração máxima desta experiência (segundos; a sessão termina automaticamente quando alcançada). Os valores permitidos são configurados pelo servidor (atualmente60/90/120, padrão60); dirigir e agir ignoram o valor (o servidor o ignora e ecoanull). Passar um valor não suportado faz o servidor retornar400000e esse início falha. Consulte a Referência da API para obter detalhes dos parâmetros.
Atuação: Definindo a Orientação do Jogador a partir aspectRatio
StartTravelData.aspectRatio é não nulo apenas para atuação: "9:16" (retrato, o padrão do lado do servidor quando o mundo é criado) ou "16:9" (paisagem); é null para aventura e direção. A tela é fixa quando o mundo for criado (especificado pelo seu servidor através da Open API), portanto, para o cliente, é um resultado somente leitura. O SDK preserva valores não reconhecidos literalmente, então o host deve tratar qualquer valor que não reconheça como null e voltar à sua própria orientação padrão.
Tempo: decida a orientação do contêiner de reprodução após startTravel() retorna e antes ligando attachVideo() e montando o devolvido SurfaceView em seu layout. O SDK só começa a vincular e renderizar o fluxo remoto quando a visualização do host é anexada, portanto, decidir a orientação nesse ponto ainda precede o primeiro quadro renderizado. O valor é entregue com o startTravel() valor de retorno e é não garantido chegar antes que o SDK entre no canal de comunicação em tempo real - ele só precisa ser aplicado antes attachVideo().
Consequência: a visualização remota está vinculada a um clipe para preencher modo de renderização - um contêiner cuja orientação discorda aspectRatio recorta a imagem (por exemplo, um 9:16 o fluxo de retrato colocado em um contêiner 16:9 perde a maior parte de sua parte superior e inferior) em vez de colocá-lo em caixa de correio.
// After startTravel() returns and before attachVideo(): size the container from aspectRatio first
val ratio: Float = when (travel.aspectRatio) { // width / height
"9:16" -> 9f / 16f // portrait (the Acting server-side default)
"16:9" -> 16f / 9f // landscape
else -> HOST_DEFAULT_RATIO // null or unrecognized: fall back to your own default orientation
}
// Apply `ratio` to the container, for example:
// - Compose: Modifier.fillMaxWidth().aspectRatio(ratio)
// - Views: put the container in a ConstraintLayout, full width, height driven by the ratio (height=0dp in XML)
binding.videoContainer.updateLayoutParams<ConstraintLayout.LayoutParams> {
dimensionRatio = ratio.toString()
}
// Only once the container orientation is settled, mount the video View returned by the SDK
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
Quando a ação não está habilitada para a conta (ou a especificação está desativada), tanto a criação quanto a adesão ao mundo são rejeitadas pelo servidor: startTravel falha com 403007 (rejeitado antes da viagem ser criada), que o SDK passa literalmente. Faça não tente novamente este código como capacidade total; solicitará a ativação.
14. Exemplo completo (ViewModel + snippet de atividade)
class TravelViewModel : ViewModel() {
private val listener = object : HappyOysterListener {
override fun onStatusChanged(status: TravelStatusValue) {
_status.value = status
}
override fun onError(error: SDKError) {
_error.value = error
}
}
init { HappyOyster.addListener(listener) }
fun start(ticket: String) = viewModelScope.launch {
try {
val travel = HappyOyster.startTravel(ticket)
_travel.value = travel
} catch (e: SDKError) {
_error.value = e
}
}
fun send(text: String) = viewModelScope.launch {
runCatching { HappyOyster.sendInstruct(text) }
}
fun stop() = viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
override fun onCleared() {
HappyOyster.removeListener(listener)
viewModelScope.launch { runCatching { HappyOyster.endTravel() } }
}
}
// Mount the video in the Activity
val videoView = HappyOyster.attachVideo()
binding.videoContainer.addView(videoView)
// On end
binding.videoContainer.removeAllViews()
15. Guia de solução de problemas de DevOps
Quando algo dá errado durante a integração (não é possível entrar em uma viagem, tela preta sem vídeo, queda de fluxo no meio da sessão, anomalias de pausa/retomada, ...), abrir os logs do SDK é a maneira mais rápida de localizar o problema. Este capítulo fornece um fluxo padrão de solução de problemas – útil tanto para sua autoverificação quanto para nos fornecer informações suficientes em uma única rodada.
15.1 Habilitando registros
Escolha uma saída de registro conforme necessário durante a solução de problemas (consulte §8 Registro): para uma autoverificação rápida, defina logcatEnabled = true e leia com adb logcat, levantando logLevel para DEBUG ou VERBOSE ao diagnosticar (o padrão INFO já carrega o cronograma completo do ciclo de vida; cair para WARN apenas para erros); para alimentar seu próprio sistema, use logHandler para receber cada LogRecord (não afetado por logLevel) e grave-o em seu arquivo ou plataforma de travamento.
15.2 ID de correlação de sessão: travelId
O travelId (i.e. StartTravelData.encryptedTravelId) nos logs do SDK é emitido em cheio. É o identificador único executado em uma sessão e a chave da sessão enviada ao servidor. É a chave para alinhar o registros do cliente com o registros de sessão do lado do servidor - inclua sempre o travelId da sessão com falha ao relatar um problema.
15.3 Coletando registros
Ao reproduzir o problema, persista os logs do SDK em um arquivo:
# Clear history, reproduce the issue, then capture SDK logs to a file
adb logcat -c
adb logcat -s HappyOysterSDK > happyoyster-sdk.log
# To also see your own app logs (replace MyApp with your tag):
adb logcat -s HappyOysterSDK MyApp > happyoyster-sdk.log
15.4 O que incluir ao relatar um problema
Para evitar múltiplas viagens de ida e volta, anexe o seguinte:
<table><colgroup><col style={{ width: "30%" }} /><col style={{ width: "70%" }} /></colgroup><thead><tr><th> Artigo </th><th> Notas </th></tr></thead><tbody><tr><td> Versão do SDK </td><td> <code>HappyOyster.VERSION</code> </td></tr><tr><td> Sessão <code>ID de viagem</code> </td><td> o <code>ID de viagem criptografado</code> da sessão com falha (ver 15.2) </td></tr><tr><td> Hora da ocorrência </td><td> o tempo aproximado (o nível de minuto está bom) </td></tr><tr><td> Código de erro </td><td> o capturado <code>SDKError.code</code> (significados na tabela de códigos de erro de referência da API) </td></tr><tr><td> Etapas de reprodução </td><td> caminho de ação + resultado esperado + resultado real </td></tr><tr><td> Meio Ambiente </td><td> modelo do dispositivo, versão do Android, rede (WiFi/celular) </td></tr><tr><td> Arquivo de registro </td><td> o <code>HappyOysterSDK</code> registro de 15.3 (<code>DEBUGAR</code>/<code>VERBOSO</code> recomendado) </td></tr></tbody></table>
15.5 Sobre redação e compartilhamento de log
Os logs do SDK são projetados para serem seguro para compartilhar: credenciais (token do portador, bilhete de viagem, token RTC), identificadores internos RTC e URLs de mídia são editados antes de serem escritos, para que nunca apareçam em texto simples; travelId é um identificador de sessão (não uma credencial) e é mantido por completo apenas para correlação. Mesmo assim, recomendamos transferir os arquivos de log por meio de um canal confiável, em vez de colá-los publicamente em plataformas não controladas.