Files
Projeto-Portaria/prompts/FASE-4-chamada.md
2026-07-22 15:55:55 -03:00

94 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FASE 4 — Videochamada
## Objetivo
LiveKit integrado ponta a ponta: emissão de tokens, sala por visita, vídeo no navegador do visitante, e degradação graciosa quando o SFU falha.
## Pré-requisitos
FASES 03 concluídas. Ler `docs/05-INFRA-DOCKER.md` §3 e §4, e `docs/01-ARQUITETURA.md` §5.5.
> **Faça a POC antes de escrever código de produção.** Suba `livekit-server` + `coturn` e feche uma chamada entre um navegador móvel em 4G real e um Android, medindo o tempo até o primeiro frame. É o maior risco técnico do projeto e o mais barato de derrubar cedo. Se a POC falhar, o problema quase sempre é a faixa UDP fechada no firewall.
## Tarefas
### 1. Porta de vídeo
```kotlin
interface VideoCallProvider {
suspend fun criarSala(visitId: UUID): RoomInfo
suspend fun emitirToken(visitId: UUID, participante: Participant): String
suspend fun encerrarSala(visitId: UUID)
suspend fun saudavel(): Boolean
}
```
Adaptador `LiveKitProvider`. A porta existe para permitir trocar por serviço gerenciado sem tocar nos apps — decisão registrada em `01-ARQUITETURA.md` ADR-003.
### 2. Emissão de tokens
Token JWT do LiveKit gerado **pelo backend**, nunca pelo cliente. Escopo mínimo:
- `roomJoin` apenas na sala daquela visita
- `canPublish` para vídeo e áudio
- `canPublishData: false`
- TTL de 10 minutos, alinhado à expiração da visita
`room_name` derivado determinísticamente de `visitId`, gravado em `visits.room_name`.
**Um token nunca dá acesso a outra sala.** Verifique isso com teste explícito.
### 3. Ciclo de vida da sala
Sala criada na transição para `EM_CHAMADA`, não antes — criar em `PENDENTE` desperdiça recurso do SFU em visitas que nunca são atendidas.
Encerrada ao chegar em estado final. `empty_timeout: 120` no LiveKit é a rede de segurança para sala órfã.
### 4. Vídeo na web do visitante
SDK `livekit-client` carregado por **`import()` dinâmico**, apenas quando a visita entra em `EM_CHAMADA`. Ele sozinho pesa mais que todo o resto do app — carregá-lo no bundle inicial destruiria o orçamento de 200KB da FASE 3.
Vídeo remoto em tela cheia, auto-preview pequeno no canto, botão de mudo e de encerrar. Indicador de qualidade de conexão.
### 5. Degradação graciosa
Circuit breaker (Resilience4j) sobre o `VideoCallProvider`. Ao abrir:
```
LiveKit fora ─► AUDIO (mesma sala, sem vídeo, muito menos banda)
└─► FOTO_TEXTO (visitante manda foto, morador aprova sem chamada)
└─► FILA_OPERADOR (resolução por telefone)
```
O nível vigente vai em `visits.degraded_mode`, é emitido como `DEGRADED_MODE` no WebSocket, aparece em `/actuator/health` e no painel admin.
**Comunique a degradação, nunca a esconda.** Esconder faz o usuário achar que o produto quebrou; nomear faz o produto parecer resiliente — que é o que ele é.
### 6. Configuração de mídia
**Limite de resolução imposto no servidor**, não no cliente: 480p a ~600kbps. 480p basta para reconhecer alguém na portaria; 1080p multiplicaria o custo de banda por quatro sem melhorar nenhuma decisão. Ver o dimensionamento em `05-INFRA-DOCKER.md` §4.
Simulcast desligado (só dois participantes). `adaptiveStream` e `dynacast` ligados. TURN configurado com o segredo do coturn.
### 7. Métricas
`portaria.call.time_to_first_frame` · `portaria.call.duration` · `portaria.call.failed_ratio` · `portaria.call.turn_usage_ratio` (uso alto de TURN indica problema de rede na portaria) · `portaria.call.degraded_ratio`.
## Critérios de aceite
- [ ] POC validada em rede real antes do código de produção
- [ ] Chamada completa entre navegador móvel em 4G e Android
- [ ] Tempo até o primeiro frame < 3s em rede boa
- [ ] Teste: token de uma sala é rejeitado em outra sala
- [ ] Teste: com LiveKit derrubado, o sistema degrada para áudio e depois para foto+texto
- [ ] Teste: sala encerrada em todos os estados finais
- [ ] SDK do LiveKit **não** aparece no bundle inicial do visitante
- [ ] Bundle inicial continua ≤ 200KB gzip
- [ ] Chamada funciona atrás de NAT simétrico (valida o coturn)
## Não faça nesta fase
- Gravação (módulo `video_recording`, FASE 8)
- App do morador (FASE 5)
- Mais de 2 participantes por sala