Skip to content

Cloud Gaming Servidor Opt-in

O Cloud Gaming dá pra cada usuário um Android 13 completo na nuvem, rodando dentro de um container Docker no seu servidor RoqueOS, com streaming de baixa latência via H.264 direto no navegador. Você instala apps como PS Remote Play, Xbox Cloud Gaming, GeForce Now, Steam Link ou Moonlight pela Play Store, e joga no que tiver navegador — TV LG, celular Android/iOS, notebook, tablet.

Por que isso é útil

Smart TVs não rodam APKs Android. PS Remote Play não tem app pra webOS. Steam Link na TV via navegador não existe. Cloud Gaming resolve isso: um Android nativo, persistente, controlado pelo seu RoqueOS, acessível via navegador de qualquer dispositivo.


Como funciona

┌──────────────────────────────────────────────────────────┐
│  Navegador (TV / celular / desktop)                      │
│  ROSAndroidStreamer.vue                                  │
│  ├── iframe (ws-scrcpy bridge → canvas H.264)            │
│  ├── useGamepadInput → Web Gamepad API                   │
│  └── useTVRemote → LG webOS keys                         │
└────────────────────────┬─────────────────────────────────┘
                         │ WebSocket binary + HTTP proxy
                         │ (auth via session token)

┌──────────────────────────────────────────────────────────┐
│  roqueos-server (NestJS)                                 │
│  AndroidGamingModule                                     │
│  ├── REST POST /android-gaming/sessions                  │
│  ├── HTTP proxy /sessions/:id/stream/*                   │
│  └── WS proxy /sessions/:id/stream (binary)              │
└────────────────────────┬─────────────────────────────────┘
                         │ Dockerode

┌──────────────────────────────────────────────────────────┐
│  Per-user containers                                     │
│  ├── Redroid (Android 13 + GApps + Play Store)           │
│  └── ws-scrcpy (bridge H.264 → WebSocket)                │
│  Volume persistente: apps + login Google sobrevivem      │
└──────────────────────────────────────────────────────────┘

Habilitar no servidor

O Cloud Gaming é opt-in — não vem ligado por padrão (consome CPU + RAM + disco). Mesma mecânica do RoqueClaw: o instalador pergunta, e cada usuário do server ganha automaticamente acesso ao seu próprio Android isolado quando você habilita.

Caminho 1 — Installer one-liner (mais fácil)

O install script oficial (https://roqueos.com.br/install.sh em Linux/macOS, https://roqueos.com.br/install.ps1 em Windows) pergunta interativamente se você quer Cloud Gaming, igual já perguntava sobre RoqueClaw:

bash
# Linux/macOS — pergunta interativa
curl -fsSL https://roqueos.com.br/install.sh | bash

# Linux/macOS — direto (sem prompt)
curl -fsSL https://roqueos.com.br/install.sh | bash -s -- --with-android-gaming

# Windows PowerShell — pergunta interativa
iwr -useb https://roqueos.com.br/install.ps1 | iex

# Windows PowerShell — direto (sem prompt)
& ([scriptblock]::Create((iwr -useb https://roqueos.com.br/install.ps1).Content)) -WithAndroidGaming

O instalador faz tudo automaticamente: pre-pull das imagens (Redroid 620MB + ws-scrcpy 800MB), seta ENABLE_ANDROID_GAMING=true no docker run, e patcha server-config.json pra persistir.

Caminho 2 — docker-compose.yml manual

Se você gerencia o server via docker-compose.yml próprio:

yaml
services:
  roqueos-server:
    image: roqueribeiro1988/roqueos-server:latest
    environment:
      - ENABLE_ANDROID_GAMING=true # ← liga a feature
      - ANDROID_GAMING_MAX_GLOBAL=5 # cap simultâneo (default 5)
      - ANDROID_GAMING_MEMORY_MB=3072 # RAM por container (default 3 GB)
      - ANDROID_GAMING_CPU_LIMIT=2.0 # CPU cores por container
    # ... resto do compose

Depois do primeiro boot, a flag fica persistida em data/server-config.json. Restart do server não desliga sozinho.

Sem subdomínio dedicado

Diferente da fase PoC (que usava redroid.level-hard.com via Cloudflare Tunnel separado), agora o stream passa pelo proxy do próprio server em /android-gaming/sessions/:id/stream. Você só precisa do seu domínio do server (ex: demo.level-hard.com, server.roqueos.com.br, etc) — exatamente igual o RoqueClaw faz com /agent/vnc. Sem zero infra externa adicional.

Imagens Docker provisionadas automaticamente

  • reppium/redroid12_x86-64_gapps:latest — Android 12 + Google Play Store (618 MB)
  • roqueribeiro1988/roqueos-ws-scrcpy:latest — Bridge oficial publicado via CI multi-arch (amd64 + arm64)

AndroidGamingService faz docker pull automático na 1ª provisão de cada container. Self-hosted users não precisam fazer build local.

Requisitos de hardware

Cada container Android consome ~3 GB RAM + 2 cores de CPU + ~8 GB disco. Servidor com 8 GB / 4 cores aguenta tranquilamente 1 usuário simultâneo. Pra 5 usuários você quer 24 GB / 16 cores. Veja Capacity planning abaixo.


Como usar

1. Abra o Cloud Gaming

No Launchpad, clique no ícone Cloud Gaming (controle de videogame verde). Se o seu server não tem ENABLE_ANDROID_GAMING=true, o app não aparece — feature gate por capabilities.

2. Aguarde o container subir

Primeira abertura leva 30-60 segundos (container Redroid bootando + ws-scrcpy iniciando + Play Store carregando). Você vê um card premium "Preparando seu Android" com progresso.

Próximas aberturas: instantâneo (container fica salvo, só reanexa o stream).

3. Faça login na Play Store

Primeira vez: abra a Play Store dentro do Android, faça login com sua conta Google. O login fica salvo pelo volume Docker persistente — não precisa repetir depois de restart.

4. Instale o app que quiser jogar

Apps recomendados:

AppO que fazOnde achar
PS Remote PlayStreamar PS5 da sua casaPlay Store
Xbox Cloud GamingCloud da Microsoft (precisa Game Pass)Play Store
GeForce NowCloud da NVIDIAPlay Store
Steam LinkStreamar PC com SteamPlay Store
MoonlightStreamar PC com NVIDIAPlay Store
ParsecStreamar PC genéricoPlay Store

5. Conecte um controle

O Cloud Gaming detecta automaticamente:

  • Xbox One / Xbox Series — USB ou Bluetooth
  • DualShock 4 / DualSense (PS4/PS5) — Bluetooth
  • Joycons / Pro Controller (Switch) — Bluetooth (precisa pareamento prévio)
  • Genéricos — qualquer controle com mapeamento Standard

Apareceu uma notificação "Controle detectado: X" no canto? Tá pronto pra jogar.

6. Jogue

A latência típica é 30-80ms em LAN + a latência do app de streaming que você escolheu. Pra LAN, dá pra jogar PS Remote Play tranquilamente.


Clique no ícone de slider no canto superior direito da janela (some depois de 2.5s sem interação — mexe o mouse pra reaparecer):

Qualidade do stream

  • Broadway.js (recomendado, default) — Decoder JavaScript puro. Funciona em qualquer navegador, inclusive TVs antigas. Bitrate 4 Mbps.
  • MSE — Media Source Extensions. Maior FPS quando suportado. Não funciona em todos os browsers.
  • Tiny H264 — Decoder mínimo. Modo leve pra dispositivos muito limitados.

Orientação

  • Automática — Segue o sensor do device (em containers M1 PoC sempre landscape).
  • Horizontal — Força orientação landscape.
  • Vertical — Força portrait. Útil quando você abre no celular em pé.

Avançado

  • Abrir seletor avançado — Mostra a UI do ws-scrcpy upstream, onde você pode escolher manualmente entre todos os players disponíveis e ver diagnóstico técnico.

Capacity planning

Cada container Android gasta:

RecursoIdleStreaming ativo
RAM~1.5 GB~2.5-3 GB
CPU0.1-0.3 cores1.5-2 cores
Disco~6 GB (volume)igual idle
Rede~50 KB/s heartbeat2-8 Mbps (depende do bitrate escolhido)

Servidor pra N usuários simultâneos: RAM ≥ 4 + 3·N GB, cores ≥ 2 + 2·N.

Quando o cap (ANDROID_GAMING_MAX_GLOBAL) é atingido, novas requisições recebem HTTP 429. O frontend mostra um card claro "Servidor cheio" com retry — sem mensagem técnica.


Persistência

Tudo o que importa pro usuário fica salvo:

  • ✅ Login Google
  • ✅ Apps instalados (Play Store mantém)
  • ✅ Configurações de cada app (PS Remote pareado com PS5, GeForce conta logada, etc)
  • ✅ Wallpapers, layout do home screen Android
  • ✅ Salvamentos in-game (do que estiver localmente no Android)

Restart do server, docker compose pull && up -d, reboot do host — tudo isso preserva o volume. Só DELETE /android-gaming/sessions/:id?purge=true apaga.

Versão do Android e volume de dados

A imagem default é redroid/redroid:14.0.0_64only-latest (Android 14, AOSP puro, sem Play Store). Trocar de versão de Android quebra o volume de dados — Android 13 → 14 ou 14 → 15 são incompatíveis no /data partition. Quando mudar a env var REDROID_IMAGE, force o purge das sessões existentes:

bash
# Listar sessions (precisa apiKey/Secret admin)
curl -H "X-API-Key: $K" -H "X-API-Secret: $S" $SERVER/android-gaming/sessions

# Purge cada sessão (perde apps instalados + login Google)
curl -X DELETE -H "X-API-Key: $K" -H "X-API-Secret: $S" \
  "$SERVER/android-gaming/sessions/<sessionId>?purge=true"

Sintoma de volume incompatível: container Redroid sobe (sys.boot_completed=1) mas o stream fica tela preta. SurfaceFlinger não consegue renderizar.


Versões de Android disponíveis

Imagem (env REDROID_IMAGE)AndroidArchPlay StoreStatus
reppium/redroid12_x86-64_gapps:latest12amd64✅ Sim (MindTheGapps)Default — validado 2026-05
sadgb/redroid-14-with_gapps:latest14arm64✅ Sim (MindTheGapps)Pra servidores ARM (Raspberry Pi 5, AWS Graviton, Apple Silicon)
redroid/redroid:15.0.0_64only-latest15amd64+arm64❌ AOSP puroOficial mais recente, sem Play
redroid/redroid:14.0.0_64only-latest14amd64+arm64❌ AOSP puroOficial
redroid/redroid:13.0.0_64only-latest13amd64+arm64❌ AOSP puroLegacy

Trocar de versão de Android quebra o /data

Containers Redroid usam um filesystem persistente em /data que é específico da versão do Android. Mudar de imagem (ex: 12 → 14) CORROMPE o volume — sintoma típico: container sobe (sys.boot_completed=1) mas a tela fica preta porque SurfaceFlinger não consegue inicializar com state antigo.

Procedimento obrigatório ao mudar REDROID_IMAGE:

bash
# 1. Listar e revogar sessões existentes (perde apps instalados + login Google)
curl -X DELETE -H "X-API-Key: $K" -H "X-API-Secret: $S" \
  "$SERVER/android-gaming/sessions/<sessionId>?purge=true"

# 2. OU wipe direto do compose dev:
cd /path/to/server
docker compose -f docker-compose.android-gaming-dev.yml down -v

# 3. Bump REDROID_IMAGE e re-up

GApps install (caminhos alternativos)

A imagem default já tem Google Play Store funcionando out-of-the-box. Esta seção é só pra quem quer customizar (imagem oficial + GApps manual, modo FOSS sem Google, etc).

Opção A — Aurora Store + F-Droid (FOSS, sem Google)

Aurora Store é um cliente alternativo open-source do Google Play que funciona sem login Google e sem GApps instalado. Aparece exatamente como uma loja de apps Android e baixa os mesmos APKs do Play Store. Para a maioria dos usos (PS Remote Play, Xbox Cloud, GeForce Now, Steam Link) atende bem.

Instalação via ADB no container Redroid (após boot):

bash
# Download F-Droid (open source app store)
curl -L -o /tmp/fdroid.apk https://f-droid.org/F-Droid.apk
docker cp /tmp/fdroid.apk <container-name>:/tmp/
docker exec <container-name> pm install -t /tmp/fdroid.apk

# Aurora Store via F-Droid:
# 1. Abrir F-Droid no Cloud Gaming
# 2. Buscar "Aurora Store"
# 3. Instalar
# 4. Em Aurora, modo "Anonymous" — entra sem login Google

Trade-off: alguns apps que dependem de Google Play Services real (Play Protect, login Google in-app) podem não funcionar.

Opção B — MindTheGapps overlay (Play Store real)

Requer download de partition2.img.gz (~600 MB) com os APKs do Google Mobile Services pré-empacotados, mount como overlay em /system_ext:

  1. Download da partition compatível com a versão Android do Redroid:
    • Android 14: https://github.com/MindTheGapps/14.0.0-arm64/releases (verifique disponibilidade)
    • Android 13: same repo, branch 13.0.0
  2. Descompactar partition2.img.gz no host
  3. Modificar AndroidGamingService.buildContainerOptions() em src/modules/android-gaming/android-gaming.service.ts pra adicionar bind mount:
    typescript
    Mounts: [{
      Source: '/path/to/partition2.img',
      Target: '/dev/redroid-gapps.img',
      Type: 'bind',
      ReadOnly: true,
    }],
  4. Bump REDROID_IMAGE se quiser Android mais novo
  5. Restart server + purge sessions antigas

Trade-off: arquivo grande, fonte externa de confiança limitada (MindTheGapps é a comunidade mais confiável), 1ª vez que abre cada usuário faz login Google.

Opção C — Imagem community-maintained com GApps integrado

Algumas comunidades publicam imagens Redroid + GApps já bundled:

  • ARM64: sadgb/redroid-14-with_gapps:latest (618 MB)
  • AMD64: reppium/redroid12_x86-64_gapps:latest (Android 12 + GApps — testado e teve issues de boot no nosso PoC; investigar antes de production)

Override via env var:

yaml
environment:
  - REDROID_IMAGE=sadgb/redroid-14-with_gapps:latest

Trade-off: dependência de comunidade não-oficial, surface de ataque maior, qualidade variável.


Usando no celular

O Redroid PoC compartilhado roda Android landscape (1280×720) — otimizado pra TV e desktop, que são as telas primárias. No celular em modo portrait, o canvas é exibido com aspect ratio preservado (letterbox top/bottom), ocupando ~30% da altura. UX funcional mas com bastante área preta.

Recomendação no mobile: rotacione o celular para landscape. O Android dentro do iframe vira fullscreen + apps de streaming (PS Remote Play, Xbox Cloud, GeForce Now, Steam Link) já são primariamente landscape mesmo. UX ideal.

Touch é gerenciado nativamente pelo ws-scrcpy via scrcpy injection protocol (não usa /dev/input direto). Coords são mapeadas do canvas físico (após CSS transforms + letterbox) para coords Android via getBoundingClientRect() — funciona em portrait letterboxed e em landscape fullscreen sem ajuste manual.

Per-user portrait Android (futuro M2)

A versão M2 (per-user containers) vai permitir cada usuário provisionar o próprio Redroid com orientation custom via env var:

bash
REDROID_WIDTH=720 REDROID_HEIGHT=1280 docker compose up

Aí o Android é nativamente portrait, fullscreen no celular, sem letterbox. Hoje (M1 PoC), todos compartilham UM container landscape.


Limitações conhecidas

M1 PoC (compartilhado)

A primeira versão usa um container Redroid compartilhado com resolução fixa 1280x720 landscape. Modo portrait força lockedVideoOrientation mas scrcpy não rotaciona fisicamente o source — pode aparecer distorcido em viewport portrait. Em M2 (per-user, em ativação), cada container é provisionado com a orientação correta.

  • WebCodecs API não funciona em iframe — pelos security policies dos browsers, o player mais rápido (WebCodecs) não tá disponível dentro do iframe do ws-scrcpy. Default = Broadway.js (puro JS, lento mas universal).
  • Sem som no PoC compartilhado — áudio do Android via ws-scrcpy ainda não está roteado. Workaround: usa o controle remoto do app de streaming pra ouvir no PS / Xbox local.
  • Sem touchscreen multi-touch — clicks únicos funcionam. Gestos de pinch/zoom ainda não atravessam o bridge.
  • Login Google requer 2FA — primeira vez precisa do código do celular. Persistência cuida do resto.

Troubleshooting

"Servidor cheio" mesmo sem ninguém online

Containers podem ter ficado órfãos. SSH no server:

bash
docker ps --filter name=roqueos-redroid --format '{{.Names}} {{.Status}}'

Se aparecer container com user que não está logado, o orphan cleanup do AndroidGamingService.cleanupOrphans() (roda no startup) deveria pegar. Force restart do server pra triggar:

bash
docker compose restart roqueos-server

Stream travado em "Preparando seu Android"

Redroid demora 30-90s pra bootar na primeira vez. Se passou 2 minutos, ver logs:

bash
docker logs roqueos-redroid-<seu-userId>

Procure por Boot completed no log. Sem isso, Android ainda tá inicializando.

Latência horrível (> 500ms)

  • Verifique se está em LAN ou via Cloudflare Tunnel — tunnel adiciona ~50-100ms.
  • Mude pra player MSE se seu browser suporta.
  • Diminua bitrate via WS_SCRCPY_IMAGE custom build (editar Dockerfile.ws-scrcpy linhas 53-60).

Login Google trava em "Verificando informações"

Conhecido — alguns containers de Android sem Play Services 100% sync precisam reset:

bash
# Pelo Cloud Gaming, abra "Configurações Android" > "Apps" > "Google Play Services" > "Forçar parada"
# Reabra a Play Store

Ou destrói + recria a sessão (perde apps instalados, mantém doc no data/server-config.json):

bash
curl -X DELETE -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" \
  "$SERVER/android-gaming/sessions/<sessionId>?purge=true"

Referências técnicas

Lançado sob a Licença MIT.