# Gravação de videochamadas O AtendroZAP pode capturar os dois sentidos de uma videochamada e gerar um MP4 após seu encerramento. É uma opção do servidor, desligada por padrão, anunciada em `capabilities.calls_video_recording`. A gravação WAV existente continua disponível com seu contrato e ciclo de vida próprios. O MP4 mostra atendente à esquerda e contato à direita, em 1280×360 a 25 fps, com vídeo H.264 e áudio AAC estéreo: voz do atendente à esquerda e do contato à direita. Proporções são preservadas com margens pretas. Câmera desligada deixa o espaço correspondente preto. O console exibe player e download na aba **Histórico de chamadas**, depois do processamento. ## Habilitar no servidor Esta configuração destina-se à homologação. A ativação em produção depende da validação com chamadas reais, orientação e consumo de recursos descrita ao final deste guia. São necessários os módulos de chamadas, vídeo e gravação de áudio: ```sh ATENDROZAP_CALLS_ENABLED=true ATENDROZAP_CALLS_VIDEO_ENABLED=true ATENDROZAP_CALLS_RECORDING_ENABLED=true ATENDROZAP_CALLS_RECORDING_DIR=/call-recordings ATENDROZAP_CALLS_RECORDING_RETENTION_DAYS=30 ATENDROZAP_CALLS_VIDEO_RECORDING_ENABLED=true ATENDROZAP_CALLS_VIDEO_RECORDING_RETENTION_DAYS=7 ``` A ativação é global por worker: as videochamadas das instâncias `calls` existentes e das cadastradas depois passam a ser capturadas automaticamente. Não há habilitação individual por instância. Chamadas anteriores à ativação não ganham gravação retroativa. Também são necessários os executáveis `ffmpeg` e `ffprobe`. A imagem padrão `api` permanece sem eles. O target opcional `api-video` inclui FFmpeg 8.1.2 e libx264; essa composição tem licença GPL-2.0-or-later e revisão própria. Não basta ligar a variável numa imagem que não contém os executáveis. Consulte a revisão de licença do repositório antes de distribuir a imagem. Depois de finalizar o commit, o fluxo do repositório pode preparar as imagens no host autorizado: ```sh deploy/ship.sh HOST REV api-video ``` `HOST` é o alias SSH autorizado e `REV` é a revisão Git revisada. Esse comando envia o código versionado e constrói as imagens da API e do console; não reinicia os containers. A construção consome recursos do host e deve ser feita na janela de preparação acordada. Ativação da imagem e da flag ocorre em etapa posterior, após homologação e autorização operacional. O serviço valida a combinação de módulos e resolve os executáveis no startup. Habilitar a opção exige publicar a imagem adequada e reiniciar a API em uma janela operacional. Esta documentação não informa que a opção já esteja ativa em produção. | Variável | Padrão | Valores aceitos | |---|---|---| | `ATENDROZAP_CALLS_VIDEO_RECORDING_ENABLED` | `false` | Booleano; requer vídeo e gravação de áudio | | `ATENDROZAP_CALLS_VIDEO_RECORDING_RETENTION_DAYS` | `7` | 1–3650 dias desde o encerramento; independente da retenção de áudio | | `ATENDROZAP_CALLS_VIDEO_RECORDING_INVERT_REMOTE_ORIENTATION` | `false` | `true` troca 90°/270° somente no cliente remoto do MP4, compatível com o ajuste `invert=true` do Atendro | | `ATENDROZAP_CALLS_VIDEO_RECORDING_FFMPEG` | `ffmpeg` | Executável ou caminho local | | `ATENDROZAP_CALLS_VIDEO_RECORDING_FFPROBE` | `ffprobe` | Executável ou caminho local | | `ATENDROZAP_CALLS_VIDEO_RECORDING_MAX_CONCURRENT` | `1` | 1–8 renders ativos por worker | | `ATENDROZAP_CALLS_VIDEO_RECORDING_MAX_PENDING` | `8` | 1–128 renders esperando por worker | | `ATENDROZAP_CALLS_VIDEO_RECORDING_MAX_BYTES` | `1073741824` (1 GiB) | 1 MiB–16 GiB; limite da captura H.264 e, separadamente, do MP4 | | `ATENDROZAP_CALLS_VIDEO_RECORDING_QUEUE_BYTES` | `16777216` (16 MiB) | 1–256 MiB de quadros aguardando escrita por chamada | | `ATENDROZAP_CALLS_VIDEO_RECORDING_RENDER_TIMEOUT` | `30m` | 1 segundo–6 horas; inclui espera por áudio e por vaga de render | Os limites de render não alteram `callsMaxConcurrent`: uma chamada encerrada pode continuar gerando seu MP4 enquanto outra chamada usa a vaga de ligação. Há também limite de entradas na fila de captura. Fila ou disco indisponível produzem falha explícita na gravação; o gravador não bloqueia a mídia. Limites de fila não substituem limites de CPU e memória do processo. ## Captura, sincronização e retomada Durante a chamada, o motor observa H.264 já codificado, timestamps RTP, orientação e mudanças de câmera. A captura usa uma fila separada e realiza escrita fora dos goroutines de mídia. O relógio de cada fluxo é alinhado à linha do tempo da chamada, preservando intervalos sem câmera; não se concatena H.264 bruto supondo uma taxa fixa de quadros. O observador pertence à chamada e permanece durante a substituição do socket do navegador. Uma retomada da ponte não inicia uma segunda gravação nem zera a linha do tempo. O material pode começar apenas no primeiro keyframe decodificável. A composição exige SPS/PPS e frames válidos; captura sem keyframe pode terminar como falha. O MP4 só é anunciado como `ready` após inspeção das trilhas e decodificação integral sem erros. Uma saída truncada ou corrompida não é publicada como gravação pronta. Nesta versão, timestamps de apresentação que exigem reordenação de quadros H.264 geram `unsupported_video_reordering`; não são descartados silenciosamente nem recebem tempos inventados. A rotação padrão usa o contrato CVO atual: quartos de volta no sentido horário. O Atendro pode aplicar uma correção adicional ao desenhar a câmera do cliente; seu controller atualmente inicia com `invert=true` quando não há preferência salva. Para usar a mesma convenção na gravação, habilitar `ATENDROZAP_CALLS_VIDEO_RECORDING_INVERT_REMOTE_ORIENTATION=true` no worker. A composição passa a usar `(4-orientação)&3` apenas no vídeo remoto: troca 90°/270° e preserva 0°/180°. O lado local, metadados da API, captura e ponte ao vivo mantêm seus contratos. O ajuste ocorre durante o processamento offline e não acrescenta uma etapa ao caminho de mídia da chamada. Essa opção não acompanha cliques manuais de rotação ou preferências locais do navegador. Ela deve corresponder à convenção de exibição da integração. Os testes sintéticos cobrem as quatro orientações, mudanças durante a chamada e quadros sem metadado novo; Android/iPhone reais e sincronização em chamadas prolongadas ainda precisam de homologação. Os segmentos originais são removidos depois do processamento; MP4 já gerados não são corrigidos automaticamente, pois a orientação de cada trecho não fica registrada no arquivo composto. ## Estado e download `GET /call/{id}` e `GET /call/history` podem incluir `videoRecording`: ```json { "callId": "CHAMADA_DE_TESTE", "videoRecording": { "status": "ready", "contentType": "video/mp4", "bytes": 1048576, "durationSeconds": 120, "downloadPath": "/call/CHAMADA_DE_TESTE/recording/video", "expiresAt": "2026-10-19T15:02:00Z" } } ``` O exemplo é um recorte sintético. O objeto `recording` continua representando somente o WAV. `video: true` indica vídeo negociado na chamada; sozinho não garante que um MP4 tenha sido gravado. `videoRecording` ausente significa que não existe registro de captura de vídeo para aquela chamada, incluindo chamadas anteriores à ativação ou sem quadros capturados. | `videoRecording.status` | Significado para a interface | |---|---| | `recording` | Capturando ou finalizando o material da chamada | | `processing` | Compondo o MP4 ou esperando vaga de processamento | | `ready` | Player e download disponíveis | | `failed` | Vídeo indisponível; `failureReason` informa uma categoria técnica | | `expired` | Retenção encerrada; preservar o histórico | Não é obrigatório observar todos os estados intermediários. Uma falha pode ocorrer durante captura ou processamento. O primeiro resultado terminal é preservado; a expiração por retenção continua sendo aplicada a um arquivo pronto. [GET /call/{id}/recording/video](https://wpp.atendro.cloud/docs/api/download-call-video-recording.md) usa exclusivamente o header `token`. Não aceita token na query. O backend do Atendro deve autenticar seu usuário, verificar a empresa e a instância e fazer proxy do arquivo, preservando `Range` e `If-Range` e as respostas `206`/`416`. O navegador não recebe a credencial da instância. O arquivo usa `Content-Type: video/mp4` e `Cache-Control: private, no-store`. O endpoint devolve `404 recording_not_available` enquanto não estiver pronto, `410 recording_expired` após a retenção e `503` se o arquivo estiver indisponível ou pertencer a outro worker. O histórico continua consultável sem sessão WhatsApp conectada. O download precisa chegar ao worker dono do arquivo; não existe transferência automática de gravações entre workers. ## Eventos e integração no Atendro Assine `call`, valide a assinatura do envelope existente e identifique a chamada por `(serverId, instanceId, data.callId)`. Os novos subeventos são `video_recording_ready` e `video_recording_failed`: ```json { "EventType": "call", "event": "call", "eventId": "ID_DO_EVENTO", "instanceId": "ID_DA_INSTANCIA", "seq": 42, "data": { "callId": "CHAMADA_DE_TESTE", "event": "video_recording_ready", "videoRecording": { "status": "ready", "contentType": "video/mp4", "durationMs": 120000, "bytes": 1048576, "failureReason": "" }, "timestamp": 1789830120 } } ``` Esse também é um recorte sintético. Falhas usam o mesmo formato, com `event: video_recording_failed`, `status: failed` e uma categoria em `failureReason`. O evento não traz URL pública nem credencial. O webhook usa `durationMs`; o histórico usa `durationSeconds`. Esses eventos atualizam **apenas a gravação de vídeo**. Não são `ended`, não mudam o motivo de encerramento, não alteram vagas de simultaneidade e não substituem `recording` do áudio. Não exigem `data.status` da chamada. Podem ser processados antes ou depois de `ended`; preserve cada domínio separado. Deduplicar por `eventId` e usar `seq` do envelope, não `data.seq`. ### Prompt para implementar no atendro-ai ```text Implemente a gravação de videochamadas do provedor AtendroZAP no atendro-ai. Leia o contrato em /docs/guias/captura-video no servidor de documentação. 1. Preserve todo o fluxo atual de chamadas e gravação WAV. Consulte a capability opcional calls_video_recording para apresentar suporte à captura; gravações antigas prontas continuam acessíveis se a flag mudar. 2. Persista videoRecording separadamente de recording, por servidor, instância e callId. Aceite recording, processing, ready, failed e expired. Ausência do objeto não pode apagar dados conhecidos nem sugerir MP4 pronto. 3. No receptor assinado EventType=call, trate data.event video_recording_ready/video_recording_failed como atualização independente. Deduplicate por eventId. Esses eventos não são ended e nunca liberam vaga, encerram atendimento ou sobrescrevem endReason/endedBy. Podem chegar antes do ended; faça upsert sem inventar estado terminal da chamada. 4. Reconcilie com GET /call/{id} ou GET /call/history ao abrir o histórico e após reconexão/perda de eventos. Use consultas com intervalo limitado enquanto processing; não espere gerar o vídeo na requisição de hangup. 5. Quando ready, exiba player de vídeo e botão de download, mantendo o áudio. Mostre Preparando vídeo durante processing, falha e expiração sem player quebrado. Webhook usa durationMs; consultas usam durationSeconds. 6. Crie proxy no backend para GET /call/{id}/recording/video no servidor correto. Valide sessão, empresa, instância e acesso à chamada; envie token só no header do backend. Replique Range/If-Range, Content-Type, Content-Range, Accept-Ranges e status 206/416; use cache privado desabilitado. Não exponha tokens no HTML, URLs, logs ou resposta ao navegador. Restrinja downloadPath à API cadastrada; não aceite URLs arbitrárias recebidas no webhook. 7. Trate 404 enquanto indisponível, 410 expirado e 503 indisponibilidade do worker sem recriar a chamada. Falha do vídeo não invalida o WAV. 8. Teste ready/failed repetidos, chegada antes/depois de ended, processing, expiração, histórico sem vídeo, isolamento entre empresas, proxy Range e preservação da contagem de ligações simultâneas. Não ligar ou migrar números reais durante esses testes. ``` ## Retenção, falhas e limites de validação WAV e MP4 usam o mesmo UUID interno, diretório privado e arquivos 0600. Os prazos são independentes e contados do encerramento: vídeo expira em **7 dias** por padrão (`ATENDROZAP_CALLS_VIDEO_RECORDING_RETENTION_DAYS`), áudio em **30 dias** (`ATENDROZAP_CALLS_RECORDING_RETENTION_DAYS`). `GET /v1/capabilities` anuncia os dois valores separadamente em `calls_video_recording_retention_days` e `calls_recording_retention_days`. A configuração vale para as novas gravações; o histórico é preservado. A limpeza horária remove os arquivos vencidos e a API recusa o download assim que o prazo termina, mesmo antes da remoção física. Durante a composição coexistem WAV, segmentos temporários e MP4: 1 GiB por captura ou saída não significa limite total de 1 GiB no disco. Exemplos de falha: `video_queue_full`, `video_render_queue_full`, `video_too_large`, `video_keyframe_unavailable`, `video_audio_unavailable`, `video_render_timeout`, `video_render_failed` ou `unsupported_video_reordering`. Há limites adicionais para arquivos H.264: até 64 segmentos, 1.024 NALs por unidade de acesso, SPS/PPS até 65.535 bytes, vídeo 8-bit 4:2:0 com até 16 referências e pixels codificados até 1920×1088 (inclui o padding de 1080p e aceita retrato). Um orçamento agregado dos buffers de decodificação limita fontes simultâneas do render; excessos geram `video_segment_limit`, `video_decoder_limit` ou `invalid_video_frame`. Esses limites protegem recursos, sem prometer uma capacidade de chamadas simultâneas. Perfis aceitos: Baseline, Main, Extended e High (66/77/88/100). Cada SPS é validado, mesmo quando há vários na mesma unidade de acesso; SPS/PPS diferentes nessa unidade e extensões MVC/SVC não suportadas são recusados. Repetições idênticas são permitidas. O processo não registra saída bruta do FFmpeg, frames, áudio ou identidades no log normal. Após reiniciar o worker, registros `recording` ou `processing` viram `failed` com `worker_restarted`, com evento de falha e limpeza dos temporários. Não há retomada automática do render. Uma falha ou expiração do vídeo não apaga o áudio nem muda o motivo de término da ligação. Quando o prazo de encerramento do arquivo (`Archive.Wait`) se esgota, os processos de render e trabalhos esperando são cancelados; não continuam como processos soltos após o encerramento do serviço. A validação com celulares reais, chamadas prolongadas, orientação e capacidade de render simultâneo na VPS continua pendente. A opção permanece desligada por padrão; ativá-la não comprova esses critérios. As evidências da implementação e seus limites estão no relatório [Gravação de videochamadas — 19/09/2026](https://wpp.atendro.cloud/docs/guias/validacao-captura-video.md).