Files
SteelXR/.lovable/plan.md
T
gpt-engineer-app[bot] 77579b8bd9 Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
2026-05-14 12:49:06 +00:00

6.5 KiB

Plano — Compartilhar tela do Quest 3 via link público

Permitir que o inspetor no Quest gere um link tipo tracksteelxr.lovable.app/watch/AB12CD, mande no WhatsApp/email, e até 3 espectadores assistam ao vivo (vídeo + áudio do Quest) em qualquer celular ou PC.

Como vai funcionar para o usuário

No Quest (broadcaster):

  1. No HUD do /xr aparece novo botão "Compartilhar" (ícone Share/Cast)
  2. Ao clicar: app gera código de 6 caracteres, mostra QR + link encurtado
  3. Botões "Copiar link" e "Compartilhar" (Web Share API → abre WhatsApp/email do sistema)
  4. Indicador vermelho 🔴 LIVE fica visível enquanto transmite, com contador de viewers
  5. Botão "Encerrar" para parar

No celular/PC (viewer):

  1. Abre o link → tela cheia com vídeo da sessão XR
  2. Sem login, sem instalar nada
  3. Mostra "Aguardando inspetor…" se a sala não estiver ativa
  4. Read-only: só assiste

Arquitetura técnica

   Quest 3 (broadcaster)              Lovable Cloud                 Viewers (celular/PC)
   ┌──────────────────┐         ┌─────────────────────┐         ┌──────────────────┐
   │ /xr              │         │ Supabase Realtime   │         │ /watch/AB12CD    │
   │ - canvas WebXR   │ ──SDP──▶│ (signaling channel) │◀──SDP── │ - <video> tag    │
   │ - getDisplayMedia│         │ rooms table         │         │ - peer connection│
   │ - RTCPeerConn x3 │◀════════ ICE candidates ═════════════▶  │                  │
   └──────────────────┘         │ viewers count       │         └──────────────────┘
            ║                   └─────────────────────┘                    ▲
            ╚════════ WebRTC P2P media stream (vídeo H264) ════════════════╝

Stack:

  • Lovable Cloud (precisa habilitar): tabela share_rooms + canal Realtime para signaling
  • WebRTC nativo do browser (RTCPeerConnection) — sem libs pesadas
  • Captura no Quest: navigator.mediaDevices.getDisplayMedia() no browser do Quest captura a janela atual (inclui o canvas 3D). Em sessão XR imersiva, alternativa é usar canvas.captureStream(30) direto do canvas WebGL — funciona melhor e não precisa de prompt
  • Sem servidor de mídia (SFU): P2P 1→3 viewers, banda do Quest aguenta tranquilo (~2 Mbps por viewer)

Estrutura de arquivos

Novos:

  • src/pages/Watch.tsx — página pública do viewer (rota /watch/:code)
  • src/components/ShareButton.tsx — botão + dialog com QR/link no HUD
  • src/lib/webrtc/broadcaster.ts — lógica do lado Quest (cria offer, gerencia múltiplos peers)
  • src/lib/webrtc/viewer.ts — lógica do lado viewer (recebe offer, monta <video>)
  • src/lib/webrtc/signaling.ts — wrapper sobre Supabase Realtime para troca SDP/ICE

Editados:

  • src/App.tsx — adicionar rota /watch/:code (pública, fora do layout XR)
  • src/components/XRHud.tsx — adicionar <ShareButton /> na toolbar
  • src/pages/XRSession.tsx — botão também visível fora do HUD
  • src/stores/useModelStore.ts — estado shareRoom: { code, viewerCount, isLive }

Schema do banco (Lovable Cloud)

create table public.share_rooms (
  code text primary key,           -- 6 chars, ex: 'AB12CD'
  created_at timestamptz default now(),
  expires_at timestamptz default now() + interval '4 hours',
  is_active boolean default true,
  viewer_count int default 0
);

-- RLS: leitura pública (viewers precisam saber se sala existe), escrita só anon controlada
alter table public.share_rooms enable row level security;
create policy "anyone reads active rooms" on public.share_rooms
  for select using (is_active and expires_at > now());
create policy "anyone creates room" on public.share_rooms
  for insert with check (true);
create policy "anyone updates count" on public.share_rooms
  for update using (true);

Signaling propriamente dito (SDP offers/answers, ICE candidates) acontece via Supabase Realtime broadcast channel room:${code} — não persiste em tabela, é efêmero.

STUN/TURN

  • STUN público do Google (stun:stun.l.google.com:19302) — grátis, resolve ~80% dos casos (NAT comum)
  • TURN: se inspetor estiver atrás de NAT simétrico (raro em fábrica), conexão falha. Para v1 deixamos só STUN. Se precisar TURN depois, integramos Twilio TURN (~$0,40/GB) ou Metered.ca (free tier 50GB/mês)

Captura no Quest — detalhes importantes

Tem dois cenários:

  1. Fora da sessão XR (modo viewer 2D): canvas.captureStream(30) — fácil, funciona já
  2. Dentro da sessão XR imersiva (passthrough AR): aqui o canvas é renderizado para o display do headset, não para a tela visível. Solução: usar XRWebGLLayer.framebuffer e renderizar uma view extra "spectator" para um canvas offscreen que vira o stream. Esta parte tem risco — pode precisar fallback para getDisplayMedia() que pede permissão do usuário no Quest

Plano: começar com cenário 1 (já cobre uso fora do AR imersivo), e cenário 2 numa segunda iteração se necessário.

Limitações honestas

  • Latência: 200-500ms (não é instantâneo, mas serve para acompanhamento)
  • Sem áudio do microfone do Quest na v1 (adicionar depois com getUserMedia({audio:true}))
  • Sala expira em 4h — geração de novo código se sessão durar mais
  • Link público: qualquer um com a URL vê. Se vazar, código pode ser revogado pelo inspetor
  • Não grava — só transmissão ao vivo. Gravação seria feature separada

Pré-requisito

Precisa habilitar Lovable Cloud (atualmente o projeto não tem). Vou pedir confirmação antes de habilitar, pois cria infraestrutura backend.

Escopo desta implementação

Botão "Compartilhar" no HUD do XR + tela inicial Geração de código + QR + link curto + Web Share API Página /watch/:code pública com vídeo Signaling via Supabase Realtime WebRTC P2P 1→3 viewers Contador de viewers em tempo real para o broadcaster Captura via canvas.captureStream (cenário fora do XR imersivo) Captura dentro da sessão XR imersiva (segunda iteração) Áudio do mic (segunda iteração) Chat / interação (você optou por read-only) TURN server (só se aparecer problema de NAT) Gravação

Aprovando o plano, eu habilito o Lovable Cloud e implemento.