diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e3c1a03 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.github +.claude +.gitattributes +Dockerfile +.dockerignore +docker-compose.yml +*.md + +# Gerado dentro do container a partir das variáveis de ambiente. +config.js +manifest.json diff --git a/.gitattributes b/.gitattributes index dfe0770..9b99870 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,8 @@ # Auto detect text files and perform LF normalization * text=auto + +# Arquivos consumidos dentro do container Linux: CRLF quebra o shebang do +# entrypoint e a configuração do nginx. +*.sh text eol=lf +Dockerfile text eol=lf +docker/* text eol=lf diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..20eff15 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,64 @@ +name: Docker + +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + +env: + REGISTRY: ghcr.io + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4 + + # O GHCR só aceita nome de imagem em minúsculas e este repositório tem + # maiúscula no nome (RadioPlayer). + - name: Nome da imagem em minúsculas + id: image + run: echo "name=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" + + - uses: docker/setup-qemu-action@v3 + + - uses: docker/setup-buildx-action@v3 + + # Em pull request só buildamos para validar; publicar é só na main/tag. + - name: Login no GHCR + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Metadados (tags e labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,format=short + + - name: Build e push + uses: docker/build-push-action@v6 + with: + context: . + # arm64 cobre Raspberry Pi e NAS ARM, público comum de self-hosting. + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9437c00 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,41 @@ +FROM nginx:1.27-alpine + +LABEL org.opencontainers.image.title="RadioPlayer" \ + org.opencontainers.image.description="Player de rádio HTML5 com now playing, capa do álbum, letra e PWA" \ + org.opencontainers.image.source="https://github.com/jailsonsb2/RadioPlayer" \ + org.opencontainers.image.licenses="AGPL-3.0-or-later" + +# Configuração padrão: sem nenhuma variável de ambiente o container sobe +# funcionando, tocando a rádio da demo. +ENV RADIO_NAME="Jailson Web Rádio" \ + URL_STREAMING="https://stream.zeno.fm/yn65fsaurfhvv" \ + API_URL="" \ + FALLBACK_API_URL="" \ + ACCENT_COLOR="" \ + BG_COLOR="" \ + THEME_COLOR="#0b0e13" + +COPY css/ /usr/share/nginx/html/css/ +COPY fonts/ /usr/share/nginx/html/fonts/ +COPY img/ /usr/share/nginx/html/img/ +COPY js/ /usr/share/nginx/html/js/ +COPY index.html service-worker.js llms.txt /usr/share/nginx/html/ + +# A AGPL-3.0 acompanha o binário: quem redistribui ou hospeda esta imagem +# precisa dela junto. +COPY LICENSE /usr/share/nginx/html/LICENSE + +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf + +# A imagem oficial do nginx executa tudo em /docker-entrypoint.d/ antes de +# subir o servidor — é onde geramos config.js e manifest.json. +COPY docker/40-radioplayer-config.sh /docker-entrypoint.d/40-radioplayer-config.sh +RUN chmod +x /docker-entrypoint.d/40-radioplayer-config.sh + +EXPOSE 80 + +# 127.0.0.1 e não localhost: dentro do container o localhost resolve primeiro +# para ::1, e o nginx escuta só em IPv4 (listen [::]:80 quebra em container sem +# IPv6) — o healthcheck levaria "connection refused" com o site no ar. +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget -q --spider http://127.0.0.1/index.html || exit 1 diff --git a/README.md b/README.md index d711c74..8a9ab5e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE) [![No API Key](https://img.shields.io/badge/API_key-not_required-orange)](#data-sources) [![PWA Ready](https://img.shields.io/badge/PWA-installable-5A0FC8)](#progressive-web-app-pwa) +[![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?logo=docker&logoColor=white)](#-docker) **[▶ Try the live demo](https://jailsonsb2.github.io/RadioPlayer/)** — a modern, dependency-free **HTML5 radio player** for any stream (**Icecast, Shoutcast, Zeno.FM, RadioJar, Azuracast** and more): the album art of the current song becomes a blurred full-page backdrop, with glass-style circular controls on top. Now playing metadata, album art, song lyrics, recently played history, **YouTube clip mode** and installable **PWA** — no Bootstrap, no jQuery, no API key. Just HTML, CSS and vanilla JavaScript. -> 🇧🇷 **Player de rádio online para o seu site — grátis e sem chave de API.** Mostra a música tocando agora com capa do álbum, letra da música, histórico das últimas tocadas e até o **clipe do YouTube sincronizado com a rádio**. Compatível com Icecast, Shoutcast, Zeno.FM e Azuracast. É só editar duas linhas e hospedar em qualquer servidor (ou de graça na Vercel/Netlify/GitHub Pages). +> 🇧🇷 **Player de rádio online para o seu site — grátis e sem chave de API.** Mostra a música tocando agora com capa do álbum, letra da música, histórico das últimas tocadas e até o **clipe do YouTube sincronizado com a rádio**. Compatível com Icecast, Shoutcast, Zeno.FM e Azuracast. É só editar duas linhas e hospedar em qualquer servidor (ou de graça na Vercel/Netlify/GitHub Pages) — ou subir com **Docker** em um comando, configurando tudo por variável de ambiente. ## Demo Screenshots @@ -46,20 +47,24 @@ * Responsive design — mobile-first single column, side-by-side layout on desktop * Progressive Web App (PWA) with an "Install app" button when the browser allows it * Accent color and surfaces themeable via CSS variables +* Single-file configuration in [config.js](config.js) — no need to touch the player source +* 🐳 **Docker image** ready for self-hosting, fully configurable by environment variables ([see below](#-docker)) ## How do I add this radio player to my website? -Open [js/script.js](js/script.js) and edit the lines below: +Open [config.js](config.js) and edit the lines below: ```javascript -// RADIO NAME -const RADIO_NAME = 'Your Radio Name'; +window.RADIO_CONFIG = { + // RADIO NAME + RADIO_NAME: 'Your Radio Name', -// Change Stream URL Here. Supports ICECAST, ZENO, SHOUTCAST, RADIOJAR and any other stream service. -const URL_STREAMING = 'https://stream.zeno.fm/yn65fsaurfhvv'; + // Change Stream URL Here. Supports ICECAST, ZENO, SHOUTCAST, RADIOJAR and any other stream service. + URL_STREAMING: 'https://stream.zeno.fm/yn65fsaurfhvv', +}; ``` -The metadata API URLs are derived from `URL_STREAMING` automatically. +The metadata API URLs are derived from `URL_STREAMING` automatically. If `config.js` is missing, the player falls back to the defaults in [js/script.js](js/script.js). ### Change Logo @@ -81,16 +86,68 @@ All the design tokens live at the top of [css/style.css](css/style.css) as CSS v ## Installation -Just put the files in your server or use Free Hosting: +Just put the files in your server, run the [Docker image](#-docker), or use Free Hosting: [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/jailsonsb2/RadioPlayer) [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/jailsonsb2/RadioPlayer) +### 🐳 Docker + +Self-hosting? Run it in one line — no need to clone the repo or edit any file: + +```bash +docker run -d -p 8080:80 \ + -e RADIO_NAME="My Radio" \ + -e URL_STREAMING="https://stream.zeno.fm/yn65fsaurfhvv" \ + --name radioplayer ghcr.io/jailsonsb2/radioplayer:latest +``` + +The player is at `http://localhost:8080`. Or with Docker Compose — see [docker-compose.yml](docker-compose.yml): + +```bash +docker compose up -d +``` + +**Environment variables** + +| Variable | Default | Description | +|---|---|---| +| `RADIO_NAME` | `Jailson Web Rádio` | Radio name (header, page title, PWA) | +| `URL_STREAMING` | Zeno.FM demo stream | Stream URL (Icecast, Shoutcast, Zeno.FM, Azuracast...) | +| `API_URL` | derived from the stream | Only if you run your own metadata API | +| `FALLBACK_API_URL` | derived from the stream | Fallback metadata endpoint | +| `ACCENT_COLOR` | `#00e1e7` | Accent color (`--accent`) | +| `BG_COLOR` | `#0b0e13` | Page background (`--bg`) | +| `THEME_COLOR` | `#0b0e13` | PWA theme color | +| `PWA_NAME` | same as `RADIO_NAME` | App name on install | +| `PWA_SHORT_NAME` | same as `PWA_NAME` | Short name (home screen icon) | + +The container generates `config.js` and `manifest.json` from these on startup, so changing a variable and restarting is enough — **no rebuild required**. + +**Your own logo** — mount it over the default cover: + +```bash +-v ./my-logo.png:/usr/share/nginx/html/img/cover.png:ro +``` + +**Building locally** instead of pulling the image: + +```bash +docker build -t radioplayer . +``` + +**Notes** + +* Built for `linux/amd64` and `linux/arm64` — Raspberry Pi and ARM NAS included. Image size ≈ 76 MB (nginx alpine + ~1.7 MB of player files). +* Ships a `HEALTHCHECK`, so Portainer / Unraid / `docker ps` show the real container health. +* Everything is served over plain HTTP on port 80 inside the container. **Put it behind a reverse proxy with HTTPS** if you expose it to the internet — browsers block the PWA install prompt and the Media Session API on insecure origins. +* The full album art, lyrics and now playing metadata still come from the public APIs listed in [Data Sources](#data-sources) — the container serves the player, not the metadata. + ### Progressive Web App (PWA) When the browser signals that installation is available, an "Install app" button appears (top-right on desktop, bottom of the screen on mobile). -**Note:** after deploying an update, bump the service worker cache version in `service-worker.js` (`CACHE_NAME`) so returning visitors get the new files. +**Note:** the service worker fetches the HTML and `config.js` from the network first, so a configuration change reaches returning visitors on the next load. After a bigger update (CSS/JS), still bump the cache version in `service-worker.js` (`CACHE_NAME`) to force everything to refresh at once. ### Configuring Radio Name and Colors (PWA) diff --git a/config.js b/config.js new file mode 100644 index 0000000..454dbc5 --- /dev/null +++ b/config.js @@ -0,0 +1,23 @@ +// Configuração do player — edite este arquivo para apontar para a sua rádio. +// +// Se você estiver rodando via Docker, NÃO edite aqui: este arquivo é gerado +// automaticamente na subida do container a partir das variáveis de ambiente +// (RADIO_NAME, URL_STREAMING, ...). Veja a seção Docker do README. +// +// Se este arquivo não existir, o js/script.js cai nos valores padrão dele. +window.RADIO_CONFIG = { + // Nome da rádio, exibido no topo e no título da página + RADIO_NAME: 'Jailson Web Rádio', + + // URL do stream. Suporta ICECAST, ZENO, SHOUTCAST, RADIOJAR e outros. + URL_STREAMING: 'https://stream.zeno.fm/yn65fsaurfhvv', + + // Opcionais: por padrão as URLs da API de metadados são derivadas do + // URL_STREAMING. Só preencha se você usa a sua própria API. + // API_URL: '', + // FALLBACK_API_URL: '', + + // Opcionais: sobrescrevem as variáveis de tema do css/style.css + // ACCENT_COLOR: '#00e1e7', + // BG_COLOR: '#0b0e13', +}; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9a845b0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +services: + radioplayer: + # Para usar a imagem pronta, troque o build por: + # image: ghcr.io/jailsonsb2/radioplayer:latest + build: . + image: ghcr.io/jailsonsb2/radioplayer:latest + container_name: radioplayer + ports: + - "8080:80" + environment: + RADIO_NAME: "Jailson Web Rádio" + URL_STREAMING: "https://stream.zeno.fm/yn65fsaurfhvv" + # ACCENT_COLOR: "#00e1e7" + # BG_COLOR: "#0b0e13" + # THEME_COLOR: "#0b0e13" + # Logo da sua rádio (192x192 ou maior, quadrado) + # volumes: + # - ./cover.png:/usr/share/nginx/html/img/cover.png:ro + restart: unless-stopped diff --git a/docker/40-radioplayer-config.sh b/docker/40-radioplayer-config.sh new file mode 100644 index 0000000..645cc01 --- /dev/null +++ b/docker/40-radioplayer-config.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# Gera config.js e manifest.json a partir das variáveis de ambiente. +# Executado pelo entrypoint da imagem oficial do nginx, antes do servidor subir. +set -e + +HTML_DIR="${HTML_DIR:-/usr/share/nginx/html}" + +# Escapa para uso dentro de uma string JSON/JS entre aspas duplas. +esc() { + printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' +} + +# --- config.js ------------------------------------------------------------- +{ + echo '// Gerado automaticamente pelo container a partir das variáveis de' + echo '// ambiente. Não edite: qualquer alteração some no próximo restart.' + echo 'window.RADIO_CONFIG = {' + echo " RADIO_NAME: \"$(esc "$RADIO_NAME")\"," + echo " URL_STREAMING: \"$(esc "$URL_STREAMING")\"," + if [ -n "$API_URL" ]; then echo " API_URL: \"$(esc "$API_URL")\","; fi + if [ -n "$FALLBACK_API_URL" ]; then echo " FALLBACK_API_URL: \"$(esc "$FALLBACK_API_URL")\","; fi + if [ -n "$ACCENT_COLOR" ]; then echo " ACCENT_COLOR: \"$(esc "$ACCENT_COLOR")\","; fi + if [ -n "$BG_COLOR" ]; then echo " BG_COLOR: \"$(esc "$BG_COLOR")\","; fi + echo '};' +} > "$HTML_DIR/config.js" + +# --- manifest.json --------------------------------------------------------- +# PWA_NAME/PWA_SHORT_NAME são opcionais; por padrão seguem o nome da rádio. +PWA_NAME="${PWA_NAME:-$RADIO_NAME}" +PWA_SHORT_NAME="${PWA_SHORT_NAME:-$PWA_NAME}" +BG="${BG_COLOR:-#0b0e13}" + +cat > "$HTML_DIR/manifest.json" <Letra +