🔄

Sincronização de Rádios

Sincronização Automática de Rádios - Radio Browser API

✅ Implementação Completa

Sistema de ingestão automática de rádios usando a Radio Browser API (https://radio-browser.info).

📊 Resultado do Teste

📻 Rádios encontradas na API: 798 (PT + BR)
✅ Rádios adicionadas: 619
⏱️  Duração: 417 segundos (~7 minutos)
❌ Erros: 0

🏗️ Arquitetura

apps/worker/src/app/radios-sync/
├── interfaces/
│   ├── radio-browser.interface.ts  # Tipos da Radio Browser API
│   └── sync.interface.ts            # Tipos de sincronização
├── schemas/
│   ├── radio.schema.ts              # Schema MongoDB
│   └── radios-sync-cursor.schema.ts  # Cursor/checkpoint (paginação)
├── services/
│   ├── radio-browser.service.ts     # Cliente API Radio Browser
│   ├── stream-validator.service.ts  # Validação de streams
│   └── radios-sync.service.ts       # Lógica principal
├── schedulers/
│   └── radios-sync.scheduler.ts     # Job diário (cron)
├── radios-sync.controller.ts        # Endpoints HTTP
└── radios-sync.module.ts            # Módulo NestJS

🚀 Funcionalidades

✅ Critérios de Aceitação Atendidos

/radios-sync/run). Renovado antes de cada página e de cada lote; expira após RADIOS_SYNC_LEASE_MSsem renovação (execução parada ou morta). Uma execução que encontra o lease ocupado não sincroniza nada: um job BullMQ fica registadocompleted com o aviso "Skipped: another radio sync run (...) holds the lease" e é adiado (moveToDelayed) para 30 s depois do fim desse lease, sem gastar tentativas; o fallback direto e o HTTP apenas registam o skip. Uma execução que perde o lease para outra para no lote seguinte (superseded`) e não desativa nada.

🎯 Radio Browser API

Países Sincronizados

Dados Extraídos

{
  name: string
  stream_url: string
  logo: string
  genres: string[]
  language: string
  country: string
  city: string
  is_active: boolean
  radio_browser_uuid: string  // UUID único para tracking
  homepage: string
  favicon: string
  bitrate: number
  codec: string
  votes: number
  clickcount: number
}

Stream Quality (probed)

Além dos campos vindos da Radio Browser API (bitrate, codec), o Worker agora persiste um snapshot de qualidade derivado da validação do stream.

Objetivo: ter um sinal consistente (0..1) para usar em ranking (search + popular) e desempate entre duplicados.

Fonte dos dados

Campos persistidos no Mongo (Radio.stream_quality)

stream_quality?: {
  bitrate_kbps?: number
  codec?: string
  hls?: boolean
  content_type?: string
  source?: 'icy-br' | 'radio-browser' | 'content-type'
  score?: number // 0..1 (normalizado via log1p(bitrate))
  last_checked?: Date
}

Atualização

As pesquisas que precisam do fallback Radio Browser convertem códigos ISO da
aplicação (por exemplo, pt/pt-PT) para o nome de idioma esperado pelo
serviço (portuguese).

📝 Como Usar

Execução Manual

# Via script
npm run sync:radios

# Via API (se worker estiver rodando)
curl -X POST http://localhost:3001/radios-sync/run

# Status
curl http://localhost:3001/radios-sync/status

Execução Automática

O job executa automaticamente todos os dias às 3:00 AM sem intervenção.

⚙️ Configuração

Scheduler

@Cron(CronExpression.EVERY_DAY_AT_3AM, {
  name: 'syncRadios',
  timeZone: 'Europe/Lisbon',
})

Parâmetros Ajustáveis

Env vars (Worker)

# Tamanho de página (limit) para paginação na Radio Browser
RADIOS_SYNC_PAGE_SIZE=500

# Orçamento de tempo por execução (ms). Quando atinge o budget, o job grava cursor e continua na próxima execução.
RADIOS_SYNC_TIME_BUDGET_MS=600000

# Duração do lease de execução única (ms), renovado antes de cada página e lote.
# Um job que encontra o lease ocupado é adiado até ao seu fim (+30 s).
RADIOS_SYNC_LEASE_MS=600000

apps/worker/src/app/radios-sync/services/radios-sync.service.ts:

SYNC_COUNTRIES = ['PT', 'BR']; // Países a sincronizar
BATCH_SIZE = 10; // Streams processados em paralelo

apps/worker/src/app/radios-sync/services/stream-validator.service.ts:

REQUEST_TIMEOUT = 8000; // 8 segundos
MAX_RETRIES = 2; // 2 tentativas

🔄 Fluxo de Sincronização

  1. Buscar rádios da Radio Browser API
    • Por país: PT e BR
  1. Validar streams

    • Verifica se stream está online
    • Timeout de 8s com 2 retries
    • Processa 10 em paralelo
  2. Atualizar base de dados

    • Adiciona novas rádios
    • Atualiza metadados existentes
    • Marca streams offline como inativos
  3. Desativar rádios obsoletas

📊 Logging

🔄 Iniciando sincronização de rádios...
🌍 Buscando rádios de PT...
✅ Encontradas 398 rádios de PT
🌍 Buscando rádios de BR...
✅ Encontradas 400 rádios de BR
📻 Total de rádios encontradas na API: 798

Processando lote 1/80
🔍 Validando stream: https://...
✅ Nova rádio adicionada: Rádio Comercial
⏭️  Stream offline, ignorando: RFM

═══════════════════════════════════════
📊 SUMÁRIO DA SINCRONIZAÇÃO
═══════════════════════════════════════
📻 Rádios encontradas na API: 798
✅ Rádios adicionadas: 619
📝 Rádios atualizadas: 0
⚠️  Rádios desativadas: 0
❌ Erros: 0
⏱️  Duração: 417.57s
═══════════════════════════════════════

🔧 Troubleshooting

Muitas rádios marcadas offline

Sincronização muito lenta

Adicionar mais países

SYNC_COUNTRIES = ['PT', 'BR', 'ES', 'FR'];

🎉 Resultado

619 rádios adicionadas de Portugal e Brasil em ~7 minutos, todas validadas e prontas para uso!