openjutsu
One-click character swap for videos: an open-source, local Higgsfield Genjutsu alternative built on ComfyUI and MiniMax H3, with FLUX.3 Klein relighting.
Ouça suas rádios online favoritas. Busque, favorite, organize e instale como app.
Translation pending
MediaGitHub @felipefmOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
Meu Player de Música
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗No silêncio que por vezes nos cerca, a busca por uma melodia, uma voz ou uma notícia se faz presente. Este projeto nasce como um humilde portal para esse universo sonoro: um Rádio Player Online, desenhado para ser simples, direto e sempre à mão.
App web de player de rádios online: busca (API Radio Browser), favoritos por categoria, estatísticas de uso, player com sleep timer e um Modo Carro (tela cheia, botões grandes, presets das rádios mais ouvidas) para usar com o celular preso no carro. A tela inicial fica com favoritos, recentes e mais ouvidas; busca e "adicionar manualmente" (uso eventual) ficam numa tela própria, aberta pelo 🔍 da barra superior. Opcionalmente toca também as músicas de um servidor Jellyfin pessoal (ver "Minha biblioteca"). HTML/CSS/JS puro, sem build e sem framework. É um PWA — instalável e com o "casco" funcionando offline.
Servir a pasta em qualquer host estático (GitHub Pages, incl. subpasta de
repositório). Todos os caminhos são relativos — não há nada para editar.
Para desenvolver, qualquer servidor local serve (python -m http.server), já
que o Service Worker não registra em file://.
| Arquivo | Responsabilidade |
|---|---|
index.html | Estrutura e marcação de todas as seções. |
style.css | Estilos. Design tokens (espaçamento, raios, cores, sombras) em :root; tema escuro e cores de destaque sobrescrevem tokens em body.dark-theme / body.title-color-*. |
app.js | Tudo o mais: player (HLS.js + <audio>), Media Session, busca e filtros, favoritos e categorias, recentes, Modo Carro, sleep timer, modais in-app, tema, PWA. |
jellyfin.js | Cliente do Jellyfin (login, listagens, URLs de áudio e capa) — window.Jellyfin. Fila e telas da biblioteca ficam no app.js. |
radioData.js | Estatísticas de uso — classe RadioAnalytics, isolada, grava em IndexedDB. window.radioData. |
wakeLock.js | Screen Wake Lock. Expõe window.RadioWakeLock; o app.js chama request()/release() ao tocar/parar. |
screensaver.js | Descanso de tela (barras estilo Winamp, capa, relógio) e o visualizador sincronizado experimental. window.RadioScreensaver. |
presence.js | Contador anônimo de ouvintes: manda o sinal enquanto toca e lê o painel do dono. window.RadioPresence. |
sw.js | Service Worker: cacheia o app shell (rede primeiro, cache como fallback offline); nunca cacheia áudio nem a API. Bump em CACHE_NAME a cada release. |
manifest.json | Metadados do PWA. |
cloudflare-worker/nowplaying.js | Worker opcional (deploy separado, fora do app shell) que lê o metadado ICY do stream pra mostrar capa/faixa na barra fixa e no Modo Carro. Ver seção abaixo. |
cloudflare-worker/presence/ | Worker opcional do contador de ouvintes (Durable Object). Ver "Ouvintes". |
Tudo local. As exceções são opcionais e descritas nas seções próprias: o "tocando agora" das rádios (Worker), a biblioteca (seu Jellyfin) e o contador anônimo de ouvintes (Worker, dá pra desligar).
localStorage
| Chave | Conteúdo |
|---|---|
radioFavorites | { categoria: [estações] } |
categoryOrder | ordem das categorias |
radioRecents | últimas 25 rádios tocadas |
radioTheme | "light" / "dark" (ausente = segue o sistema) |
radioTitleColor | cor de destaque |
radioSearchFilters | último país/tag/idioma usados |
radioVolume | último volume |
radioJellyfin | endereço do Jellyfin, usuário, token da sessão e qualidade. A senha nunca é salva. |
radioJellyfinPlayed | ids das últimas ~400 faixas tocadas (o "tocar tudo no aleatório" evita repetir) |
radioScreensaverStyle | estilo do descanso de tela (hoje só bars) |
radioPresenceId | código aleatório deste aparelho no contador de ouvintes |
radioPresenceOff | "1" = não enviar o sinal anônimo |
radioPresenceToken | chave do painel de ouvintes (só no aparelho do dono; também o marca como "seu") |
IndexedDB — banco radioAnalyticsDB, store analytics: clicks e tempo de
escuta por estação. Alimenta "Estatísticas" e "Top mais ouvidas".
O "Exportar favoritos" cobre só radioFavorites. O "Exportar dados" (em
Estatísticas) cobre só o IndexedDB. Não há backup unificado.
http:// não toca em página servida por https://
(bloqueio de conteúdo misto do navegador) — comum em rádios pequenas. O player
tenta automaticamente a versão https:// e a URL alternativa da estação antes
de desistir, então isso só afeta rádios sem HTTPS de verdade.O app é feito e testado para o Chrome no Android. Alguns recursos dependem de APIs que outros navegadores não têm ou limitam — nesses casos o recurso simplesmente não aparece, sem quebrar o resto:
| Recurso | Android (Chrome) | iPhone (Safari) |
|---|---|---|
| Modo Carro em tela cheia (esconde barra de status/navegação) | ✅ Fullscreen API | ❌ Safari só permite tela cheia em vídeo; as barras continuam visíveis |
| Capa/faixa na notificação e tela de bloqueio | ✅ Media Session | ⚠️ parcial |
| Capa/faixa no painel do carro (Bluetooth) | ✅ se o carro suportar (ver abaixo) | ⚠️ parcial |
| Botões do volante (play/stop, próxima/anterior) | ✅ | ⚠️ parcial |
| "Voltar" fecha a busca/biblioteca/Modo Carro em vez de sair do app | ✅ | n/a |
O app publica o que está tocando pela Media Session API — o Android repassa isso para a notificação, para a tela de bloqueio e, via Bluetooth (AVRCP), para o painel do carro:
O Modo Carro pede tela cheia ao abrir e de novo a cada toque nele — o Android sai da tela cheia ao alternar para outro app (GPS, por exemplo), e o navegador só permite voltar a ela a partir de um toque.
Por padrão o player mostra só nome da estação, país/codec/bitrate (na barra fixa) e tempo de escuta (no Modo Carro). Pra mostrar também "Artista — Faixa" e a capa do álbum — na barra fixa e no Modo Carro — é preciso um pequeno proxy, porque o navegador não consegue ler o metadado ICY que o Shoutcast/ Icecast intercala dentro do áudio bruto — isso só dá pra ler no servidor.
cloudflare-worker/nowplaying.js como um Worker no Cloudflare
(dashboard → Workers & Pages → Create application → Create Worker, cole o
arquivo, publique).ALLOWED_ORIGINS no topo do arquivo para o(s) domínio(s) reais do
app antes de publicar.NOWPLAYING_WORKER_URL, no topo
de app.js.Sem isso configurado (valor padrão: string vazia), o app funciona normalmente, só sem capa/faixa — nada quebra. Quando a estação não manda metadado (rádio falada, por exemplo) ou não dá pra achar a capa, o app também cai de volta no comportamento padrão silenciosamente. A busca de capa usa a API pública da iTunes (sem chave, sem custo) como alternativa para estações que não mandam a própria arte junto do metadado.
Toca as músicas de um servidor Jellyfin pessoal dentro do app: playlists, álbuns, artistas, busca, "tocar tudo no aleatório" e "mix parecido" (o Instant Mix do Jellyfin). Fica invisível até ser conectada: o botão 🎵 da barra superior só aparece depois de conectar em Configurações → Minha biblioteca.
Por que o Tailscale Serve: o app é servido em HTTPS, e o navegador
bloqueia áudio e chamadas HTTP numa página HTTPS — então
http://ip-do-servidor:8096 não funciona. O Tailscale Serve dá ao servidor
um endereço https://<máquina>.<tailnet>.ts.net com certificado válido,
acessível só de dentro da tailnet (nada fica exposto na internet). Em casa o
Tailscale conecta direto pela rede local, então não há perda de velocidade.
Configuração (uma vez só):
No painel do Tailscale (admin → DNS), ligar MagicDNS e HTTPS Certificates.
No servidor, publicar a porta do Jellyfin. Com o Tailscale num container
em modo host (app da BigBear no CasaOS), a regra precisa ficar no
arquivo que o container lê ao subir (TS_SERVE_CONFIG=/config/serve-config.json).
Um tailscale serve feito à mão só vale até o container reiniciar: com o
arquivo ausente, ele sobe sem regra nenhuma. Crie a regra e grave-a no
arquivo:
sudo docker exec big-bear-tailscale tailscale serve --bg 8096
F=/DATA/AppData/big-bear-tailscale/config/serve-config.json
sudo docker exec big-bear-tailscale tailscale serve status --json | sudo tee "$F"
Para conferir que sobrevive a um reinício:
sudo docker restart big-bear-tailscale
sleep 15
sudo docker exec big-bear-tailscale tailscale serve status
Deve mostrar https://<máquina>.<tailnet>.ts.net → proxy http://127.0.0.1:8096.
Sintoma de regra perdida: o Jellyfin funciona na rede de casa, mas o
app não conecta nem toca, serve status diz "No serve config" e o log do
container (docker logs big-bear-tailscale) mostra
could not connect to local backend server at 127.0.0.1:443. Refaça os
comandos acima.
Ao renomear a máquina no Tailscale, rode tailscale serve reset e os
comandos acima de novo — a regra (e o arquivo) guardam o nome antigo
(sintoma: "404 not found" no endereço novo).
No app: ⚙️ → Minha biblioteca → Conectar servidor, com o endereço https://
do passo 2, usuário e senha do Jellyfin. O celular precisa estar com o
Tailscale ligado (no Android, "VPN sempre ativa" evita esquecer).
Detalhes:
radioJellyfinPlayed) e
para no fim do lote.localStorage deste aparelho e vai
na URL do áudio (o <audio> não manda headers). Ele vale só pro seu
servidor e pode ser revogado no Jellyfin (Painel → Dispositivos) ou com
"Desconectar" no app.Etag do item (v=), pra que um
arquivo corrigido no servidor não seja misturado com pedaços antigos no
cache do Chrome.ffprobe mostra Could not read mimetype from an attached picture). O
Chrome recusa o arquivo inteiro. Correção no servidor, sem reconverter o
áudio: ffmpeg -i in.flac -map 0:a -c copy -map_metadata 0 out.flac. O app
não contorna isso de propósito — mostra o erro com o detalhe do navegador
pra que o arquivo seja corrigido.now using …) e linhas
derp-11 does not know about peer. Hipóteses: troca automática de banda
2,4/5 GHz do modem e o modo TS_USERSPACE=true do container, que lida mal
com essas trocas. O buffer da próxima faixa (acima) reduz o efeito; ainda
não há proteção pra uma queda no meio da faixa atual. No momento do
travamento, rodar:
sudo docker exec big-bear-tailscale tailscale status
sudo docker logs --since 5m big-bear-tailscale 2>&1 | grep -iE "proxy|timed out|error|derp|now using"
relay em vez de direct, ou muitas linhas now using/derp, confirmam
que é a conexão. Próximos passos possíveis, se continuar: detector de
travamento com reconexão visível na faixa atual, buffer de 2 faixas, e
tirar o modo userspace do Tailscale no servidor.Botão 🌌 no Modo Carro e no painel do player (▴). Tela cheia preta com barras estilo Winamp (com "picos" que caem devagar), capa, nome do que toca e relógio; o bloco de informações muda de lugar a cada 20 s pra não marcar a tela. Toque na tela (ou "voltar") pra sair. Mantém a tela ligada enquanto aberto.
O modo é escolhido sozinho pelo que está tocando:
OfflineAudioContext) e o espectro (FFT de 2048 pontos, 32 faixas
de 40 Hz a 16 kHz) e a onda são calculados no ponto em que o player está.
O <audio> que toca não é ligado ao Web Audio: isso deixaria mudas as
rádios sem CORS e poderia atrapalhar tocar com a tela desligada. Custo: se
a faixa não estiver na memória (buffer), ela é baixada de novo; a análise
ocupa ~40 MB por faixa de 4 min (liberados ao sair). Faixas com mais de
15 min ficam na animação. No canto aparece "analisando a faixa…" e depois
"sincronizado com a música".Estilos: ficam em STYLES, no screensaver.js. Cada estilo é uma função
draw que recebe os níveis já suavizados (barras e picos, 0 a 1), a onda (só
no sincronizado) e a cor de destaque. Hoje há um, "Barras (Winamp)"; um
estilo novo aparece sozinho em Configurações → Descanso de tela.
O app é público: qualquer pessoa com o link ouve as rádios (a biblioteca depende do seu Tailscale e login). Dois jeitos de saber se há mais gente:
1. Cloudflare Web Analytics (visitas por dia, país, aparelho). O domínio
não está na conta da Cloudflare, então o modo automático não serve: o
index.html carrega o script da Cloudflare (beacon.min.js, com o token do
site — público por natureza). Os números ficam no painel da Cloudflare →
Analytics & Logs → Web Analytics. Não usa cookies.
2. "Ouvindo agora" no app (Configurações → 👥 Ouvintes). Enquanto algo
toca, o app manda a cada 2 min um sinal anônimo pro Worker
cloudflare-worker/presence: um código aleatório do aparelho, o nome da
rádio (faixas da biblioteca aparecem só como "Minha biblioteca"), o tipo de
aparelho e se é um aparelho do dono. A Cloudflare acrescenta país e cidade
aproximados. Ao parar ou fechar, o app avisa que saiu; sem sinal por 5 min, o
aparelho some da lista. Por dia, o Worker guarda só os códigos dos aparelhos
que usaram (60 dias), pra contagem. O aviso aparece nas Configurações e dá pra
desligar o envio ali.
Só quem tem a chave do painel vê a lista (quem está ouvindo, de onde e há quanto tempo, e aparelhos por dia). Ter a chave no aparelho também o marca como "seu" na contagem.
Deploy do Worker (uma vez só, na pasta cloudflare-worker/presence):
npx wrangler login
npx wrangler deploy
npx wrangler secret put READ_TOKEN # invente uma chave longa
Depois: cole a URL do Worker em PRESENCE_WORKER_URL no topo do app.js,
publique, e no app vá em ⚙️ → 👥 Ouvintes → "Tenho a chave do painel". Com a
URL em branco, o app não manda nada e a seção diz que não está configurado.
Usa Durable Object com SQLite, disponível no plano gratuito. Tudo fica no
armazenamento do objeto (não só na memória): a Cloudflare o tira da memória
quando fica parado entre os sinais de 2 min.
CC BY-NC 4.0 — livre para compartilhar e adaptar, com atribuição e para uso não comercial.