- JavaScript 78%
- Shell 22%
- Remove src/player.js: 86 linhas que ninguem importava (era para a API HTTP adiada; volta do historico quando a etapa chegar). - mpv.js: tira observe()/property-change e waitFor(), que nada usava, junto com a re-assinatura de propriedades na reconexao. Funde get()+tryGet() num get(prop, fallback) so, e o emit generico de eventos cobre o que o caso especial de property-change fazia. - MpvPlayer: um try/finally no lugar de dois try aninhados (o cancel() do #waitLoad e idempotente); remove o getter statusFromMpv, morto. - store.js: troca o cache em memoria + leitura compartilhada por releitura dentro da fila de escrita. Mesma garantia contra as duas sessoes se atropelarem, com bem menos estado para acompanhar. - dial.js: pinDialUuid() calcula o UUID sozinho, em vez de exigir duas chamadas encadeadas. Verificado depois: play/pause/seek/stop 2/2, UDN inalterado, screenIds intactos e um unico listener de end-file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| docs | ||
| scripts | ||
| src | ||
| systemd | ||
| .gitignore | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
homecast
Transformar esta máquina (Ubuntu 26.04, ligada na TV por HDMI) num alvo de transmissão para o celular Android — substituindo a Android TV que fica resetando de fábrica por problema de hardware.
Hardware / ambiente detectado
| Item | Valor |
|---|---|
| SO | Ubuntu 26.04 LTS (resolute), kernel 7.0 |
| Host | jozz, IP LAN 192.168.1.107 (Wi-Fi wlx3c52a1462bd0) |
| GPU | AMD Radeon 540/550 (amdgpu), /dev/dri/card1 |
| Saída | card1-HDMI-A-1 — connected + enabled |
| Sessão | Plasma Wayland (kwin_wayland, socket wayland-0, Xwayland :0) no tty1/seat0, sempre logada |
| Áudio | PipeWire; sink HDMI = alsa_output.pci-0000_01_00.1.hdmi-surround-extra3 |
| Firewall | ufw ativo → precisa de regras para mDNS/SSDP (ver docs/plano.md) |
| Player | mpv 0.41, VLC, ffmpeg, yt-dlp 2026.03.17 |
| Runtime | Node 22.22, Python 3, avahi-daemon ativo (essencial p/ mDNS) |
Conclusão da pesquisa (resumo)
Não dá para virar um Chromecast "de verdade" para apps Android (Stremio,
etc.): o Cast v2 exige device authentication — o receptor precisa apresentar
um certificado assinado pela ICA da Google, cuja chave privada só a Google tem.
Não é engenharia reversa que resolve; é criptografia. Detalhes e fontes em
docs/pesquisa.md.
Por isso o projeto usa um protocolo diferente para cada origem:
| Origem no celular | Protocolo | Componente | Viabilidade |
|---|---|---|---|
| App YouTube | DIAL + Lounge API | yt-cast-receiver (Node) + mpv |
✅ nativo, aparece no botão de cast |
| Stremio | UPnP/DLNA (via "abrir em player externo") | gmediarender ou renderer próprio + mpv |
✅ com app intermediário no celular |
| Qualquer link / navegador | HTTP simples | API POST /play própria + mpv |
✅ atalho/PWA no celular |
| Espelhamento de tela | Cast mirroring / Miracast | shanocast (só Chrome desktop) / MiracleCast |
⚠️ frágil |
Arquitetura
celular Android
┌──────────┬───────────┬──────────────┐
│ YouTube │ Stremio │ navegador / │
│ (DIAL) │ (→ DLNA) │ atalho HTTP │
└────┬─────┴─────┬─────┴──────┬───────┘
│ │ │ LAN 192.168.1.107
┌────▼───────────▼────────────▼───────┐
│ homecast (este repo) │
│ ├─ receptor YouTube (Node/DIAL) │
│ ├─ renderer DLNA │
│ └─ API HTTP /play │
└───────────────┬─────────────────────┘
│ IPC (socket unix)
┌────▼─────┐
│ mpv KMS │──HDMI──▶ TV
│ --vo=gpu│
└──────────┘
Um único mpv persistente na sessão Plasma Wayland, controlado por
--input-ipc-server, é o back-end de reprodução comum aos três caminhos.
Validado: WAYLAND_DISPLAY=wayland-0 XDG_RUNTIME_DIR=/run/user/1000 mpv ...
funciona a partir de um shell SSH, sem root e sem tocar em DRM/KMS.
Status
- Levantamento do hardware e do que já está instalado
- Pesquisa dos projetos existentes (
docs/pesquisa.md) - mpv persistente na sessão Plasma +
homecast-mpv.service - Receptor YouTube funcionando (
homecast-youtube.service), descoberta por DIAL — aparece comohomecast (jozz)no botão de transmitir - API HTTP
/play(etapa 2) - Renderer DLNA para o Stremio (etapa 4)
- Espelhamento de tela (etapa 5, opcional)
Armadilhas já resolvidas (não repetir)
~/.local/bin/yt-dlpé um symlink pipx quebrado (o venv sumiu). Oytdl_hookdo mpv falhava com "not found or not enough permissions" e não saía um frame. Use/usr/bin/yt-dlp.- O
yt-cast-receivernão repassauuidpara opeer-dial, que então sorteia um UUID por start: cada reinício virava uma TV nova na lista do app do YouTube. Resolvido emsrc/youtube/dial.js, com UUID derivado domachine-id. - O
DataStorepadrão grava em.node-persist/relativo ao cwd. Trocado porsrc/youtube/store.js, em~/.local/state/homecast/. - A lib registra duas telas (YouTube e YouTube Music), com o mesmo nome — é normal aparecerem duas entradas com o mesmo rótulo.
- A saída fica em 3840x2160@30 de propósito: em 4K@60 o áudio HDMI para de
funcionar (limite de banda do link). Não "conserte" isso mexendo no
kscreen-doctor. - Como a tela é 30 Hz, vídeo de 60fps mostra ~30 frames descartados por segundo
no
frame-drop-count. É esperado e não é perda de qualidade de imagem. Não vale forçar formatos de 30fps para zerar esse número: no YouTube as faixas de 30fps costumam ser de resolução menor (medido: um vídeo caiu de 1080p para 854x480). Resolução ganha de taxa de quadros aqui. - O 403 do YouTube em URLs assinadas é intermitente (~1 em 3); quem cuida
disso é o retry do
doPlay, não configuração de formato.
Passo a passo em docs/plano.md.