Release v1.0.11: Pause-Fix, OAuth-Assistent, Docs, Cleanup
Pause-Fix (Silence-Injektor): - Python-Skript zwischen librespot und ffmpeg - Bei Pause füllt der Injektor die Stille mit Null-Bytes - Icecast bleibt verbunden, Stream reißt nicht ab OAuth-Einrichtungs-Assistent (Port 5589): - oauth_helper.py: HTTP-Server mit geführtem Anmelde-Prozess - Zeigt Spotify-Auth-Link automatisch sobald librespot ihn ausgibt - URL-Fixer: Nutzer fügt 127.0.0.1-URL ein, Assistent korrigiert auf HA-IP - Erkennt bereits vorhandene Credentials und zeigt Hinweis Dokumentation: - DOCS.md für die HA Add-on Detailseite erstellt - README.md vollständig überarbeitet und aktualisiert Cleanup: - Leere Testdatei entfernt - Veraltete example-config.yaml entfernt - manifest.json der Custom Component auf v1.0.11 gebracht Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
125
README.md
125
README.md
@@ -16,10 +16,7 @@ Das **Busch-Jäger Unterputz-Internetradio** kann nur Internetradio-Streams absp
|
||||
|
||||
## Lösung
|
||||
|
||||
Dieses Projekt besteht aus zwei Teilen:
|
||||
|
||||
1. **Home Assistant Add-on** – startet ein virtuelles Spotify Connect-Gerät ([librespot](https://github.com/librespot-org/librespot)) und stellt das Audio als MP3-Stream über Icecast bereit
|
||||
2. **Custom Component** (optional) – zeigt den Stream-Status als Media-Player-Entity in HA an
|
||||
Dieses Home Assistant Add-on erstellt ein virtuelles Spotify Connect-Gerät ([librespot](https://github.com/librespot-org/librespot)) und stellt das Audio als MP3- oder AAC-Stream über Icecast bereit.
|
||||
|
||||
### Funktionsweise
|
||||
|
||||
@@ -53,46 +50,39 @@ Spotify-App → [Spotify Connect] → librespot → PCM → ffmpeg → Icecast2
|
||||
|
||||
```yaml
|
||||
device_name: "Busch-Radio" # Name in der Spotify-App
|
||||
bitrate: 320 # 96 / 160 / 320 kbps
|
||||
bitrate: 160 # 96 / 128 / 160 / 192 kbps
|
||||
stream_format: "mp3" # mp3 oder aac
|
||||
stream_port: 8000 # HTTP-Port des Streams
|
||||
stream_mount: "/stream.mp3" # URL-Pfad
|
||||
stream_mount: "/stream" # URL-Pfad
|
||||
icecast_password: "geheim" # Icecast-Passwort (bitte ändern)
|
||||
```
|
||||
|
||||
### 3. Add-on starten & Spotify autorisieren
|
||||
|
||||
Beim **ersten Start** erscheint im Log eine Anleitung. Folge diesen Schritten:
|
||||
Beim **ersten Start** erscheint im Log:
|
||||
|
||||
1. Im Log erscheint eine Zeile wie:
|
||||
```
|
||||
Browse to: https://accounts.spotify.com/authorize?...
|
||||
```
|
||||
Kopiere diese URL vollständig und öffne sie im Browser.
|
||||
```
|
||||
Öffne die Einrichtungs-Seite im Browser:
|
||||
http://<homeassistant-ip>:5589
|
||||
```
|
||||
|
||||
2. Melde dich mit deinem Spotify-Konto an und erteile die Berechtigung.
|
||||
Öffne diese Adresse im Browser. Der **Einrichtungs-Assistent** führt dich durch die Spotify-Anmeldung:
|
||||
|
||||
3. Dein Browser wird danach weitergeleitet auf eine URL wie:
|
||||
```
|
||||
http://127.0.0.1:5588/login?code=...
|
||||
```
|
||||
Diese Seite schlägt fehl – **das ist normal**.
|
||||
1. Klicke auf **„Mit Spotify anmelden"**
|
||||
2. Melde dich mit deinem Spotify-Konto an und bestätige die Berechtigung
|
||||
3. Dein Browser wird auf `http://127.0.0.1:5588/login?code=...` weitergeleitet – diese Seite ist nicht erreichbar
|
||||
4. Kopiere die URL aus der Adressleiste und füge sie auf der Einrichtungs-Seite ein
|
||||
5. Klicke **„Weiter"** – der Assistent leitet die URL korrekt weiter
|
||||
6. Bei Erfolg erscheint: **Autorisierung erfolgreich!**
|
||||
|
||||
4. **Ersetze** in der Adressleiste `127.0.0.1` durch die IP deines Home-Assistant-Servers und drücke Enter:
|
||||
```
|
||||
http://<homeassistant-ip>:5588/login?code=...
|
||||
```
|
||||
|
||||
5. Bei Erfolg zeigt der Browser: `Autorisierung erfolgreich!`
|
||||
Das Add-on startet danach automatisch durch.
|
||||
|
||||
Die Credentials werden dauerhaft unter `/data/librespot-cache/` gespeichert — **dieser Schritt ist nur einmalig nötig**.
|
||||
Die Credentials werden dauerhaft gespeichert – **dieser Schritt ist nur einmalig nötig**.
|
||||
|
||||
### 4. Stream im Radio eintragen
|
||||
|
||||
Trage folgende URL im Busch-Jäger Radio ein:
|
||||
|
||||
```
|
||||
http://<homeassistant-ip>:8000/stream.mp3
|
||||
http://<homeassistant-ip>:8000/stream
|
||||
```
|
||||
|
||||
### 5. Musik abspielen
|
||||
@@ -126,21 +116,32 @@ Dann unter **Einstellungen → Integrationen → Busch-Radio Spotify Bridge** ei
|
||||
|
||||
---
|
||||
|
||||
## Nützliche URLs
|
||||
|
||||
| URL | Funktion |
|
||||
|---|---|
|
||||
| `http://<ha-ip>:5589` | Einrichtungs-Assistent (OAuth) |
|
||||
| `http://<ha-ip>:8000/stream` | Stream-URL für das Radio |
|
||||
| `http://<ha-ip>:8000` | Icecast-Statusseite |
|
||||
|
||||
---
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Komponenten
|
||||
|
||||
| Komponente | Funktion |
|
||||
|---|---|
|
||||
| [librespot](https://github.com/librespot-org/librespot) v0.7.0 | Spotify Connect Client (PCM-Ausgabe via Pipe) |
|
||||
| [ffmpeg](https://ffmpeg.org) | PCM → MP3 Transkodierung |
|
||||
| [librespot](https://github.com/librespot-org/librespot) main | Spotify Connect Client (PCM-Ausgabe via Pipe) |
|
||||
| [ffmpeg](https://ffmpeg.org) | PCM → MP3/AAC Transkodierung |
|
||||
| [Icecast](https://icecast.org) | HTTP-Stream-Server |
|
||||
|
||||
### Signalfluss
|
||||
|
||||
```
|
||||
Spotify CDN → librespot (dekodiert, PCM auf stdout)
|
||||
→ ffmpeg (44100 Hz / S16LE / Stereo → MP3 320k)
|
||||
→ silence-injector (hält Stream bei Pause aufrecht)
|
||||
→ ffmpeg -re (Echtzeit-Encoding → MP3/AAC)
|
||||
→ Icecast (HTTP-Stream)
|
||||
→ Busch-Jäger Radio
|
||||
```
|
||||
@@ -148,7 +149,7 @@ Spotify CDN → librespot (dekodiert, PCM auf stdout)
|
||||
### Hinweise zum Build
|
||||
|
||||
- librespot wird aus dem Quellcode kompiliert (Rust, ~10–15 Min.)
|
||||
- Der OAuth-Server wird beim Build gepatcht: bindet an `0.0.0.0` statt `127.0.0.1`, damit der Browser-Login aus dem Heimnetz funktioniert
|
||||
- Der OAuth-Callback-Server bindet an `0.0.0.0` statt `127.0.0.1`, damit der Browser-Login aus dem Heimnetz funktioniert
|
||||
- Credentials werden in `/data/librespot-cache/` persistent gespeichert
|
||||
|
||||
---
|
||||
@@ -157,14 +158,41 @@ Spotify CDN → librespot (dekodiert, PCM auf stdout)
|
||||
|
||||
| Problem | Lösung |
|
||||
|---|---|
|
||||
| „Busch-Radio" erscheint nicht in Spotify | OAuth-Autorisierung abschließen (Logs prüfen) |
|
||||
| Stream-Seite nicht erreichbar | Icecast-Status: `http://ha-ip:8000` |
|
||||
| OAuth-Weiterleitung schlägt fehl | Normal – `127.0.0.1` in Adressleiste durch HA-IP ersetzen |
|
||||
| Stream bricht bei Pause ab | Normal – Radio nach Fortsetzen neu verbinden |
|
||||
| „Busch-Radio" erscheint nicht in Spotify | Einrichtungs-Seite öffnen: `http://<ha-ip>:5589` |
|
||||
| Stream nicht erreichbar | Icecast-Status prüfen: `http://<ha-ip>:8000` |
|
||||
| Stream bricht bei Pause ab | Auf Version 1.0.11+ aktualisieren |
|
||||
| Songs wechseln zu schnell | Auf Version 1.0.10+ aktualisieren |
|
||||
| Neu autorisieren | Add-on stoppen → `/data/librespot-cache/credentials.json` löschen → neu starten |
|
||||
| Build schlägt fehl | Logs im Add-on Store prüfen |
|
||||
|
||||
---
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```
|
||||
busch-radio-spotify/
|
||||
├── busch_radio_spotify/ # Home Assistant Add-on
|
||||
│ ├── Dockerfile # Multi-Stage Build (Rust + Alpine)
|
||||
│ ├── config.yaml # Add-on Manifest & Optionen
|
||||
│ ├── build.yaml # Multi-Arch Konfiguration
|
||||
│ ├── DOCS.md # Add-on Dokumentation (in HA angezeigt)
|
||||
│ ├── run.sh # Startup-Script
|
||||
│ ├── oauth_helper.py # OAuth-Einrichtungs-Assistent (Port 5589)
|
||||
│ ├── icon.png # Add-on Icon (Store)
|
||||
│ └── logo.png # Add-on Logo (Detailseite)
|
||||
├── custom_components/ # Optionale HA Integration
|
||||
│ └── busch_radio_spotify/
|
||||
│ ├── __init__.py
|
||||
│ ├── manifest.json
|
||||
│ ├── media_player.py
|
||||
│ ├── config_flow.py
|
||||
│ └── translations/
|
||||
├── repository.yaml # HA Repository-Manifest
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rechtliche Hinweise
|
||||
|
||||
- Dieses Projekt nutzt **librespot**, eine Open-Source-Reimplementierung des Spotify-Clients
|
||||
@@ -174,31 +202,6 @@ Spotify CDN → librespot (dekodiert, PCM auf stdout)
|
||||
|
||||
---
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```
|
||||
busch-radio-spotify/
|
||||
├── busch_radio_spotify/ # Home Assistant Add-on
|
||||
│ ├── Dockerfile # Multi-Stage Build (Rust + Alpine)
|
||||
│ ├── config.yaml # Add-on Manifest & Optionen
|
||||
│ ├── build.yaml # Multi-Arch Konfiguration
|
||||
│ ├── run.sh # Startup-Script
|
||||
│ ├── icon.png # Add-on Icon (Store)
|
||||
│ └── logo.png # Add-on Logo (Detailseite)
|
||||
├── custom_components/ # Optionale HA Integration
|
||||
│ └── busch_radio_spotify/
|
||||
│ ├── __init__.py
|
||||
│ ├── manifest.json
|
||||
│ ├── media_player.py
|
||||
│ ├── config_flow.py
|
||||
│ └── translations/
|
||||
├── example-config.yaml # Beispielkonfiguration
|
||||
├── repository.yaml # HA Repository-Manifest
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lizenz
|
||||
|
||||
MIT License
|
||||
|
||||
89
busch_radio_spotify/DOCS.md
Normal file
89
busch_radio_spotify/DOCS.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Busch-Radio Spotify Bridge
|
||||
|
||||
Streamt Spotify-Musik als Internetradio-Stream für das Busch-Jäger Unterputz-Internetradio.
|
||||
|
||||
Ein virtuelles Spotify Connect-Gerät (librespot) nimmt Audio entgegen und stellt es per Icecast als HTTP-Stream bereit.
|
||||
|
||||
```
|
||||
Spotify-App → librespot → ffmpeg → Icecast → Busch-Jäger Radio
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Erste Einrichtung
|
||||
|
||||
### 1. Add-on konfigurieren und starten
|
||||
|
||||
Passe die Optionen an (siehe unten) und starte das Add-on.
|
||||
|
||||
### 2. Spotify autorisieren (einmalig)
|
||||
|
||||
Öffne den Einrichtungs-Assistenten im Browser:
|
||||
|
||||
```
|
||||
http://<homeassistant-ip>:5589
|
||||
```
|
||||
|
||||
Der Assistent führt dich Schritt für Schritt durch die Anmeldung. Die Credentials werden dauerhaft gespeichert – dieser Schritt ist nur beim ersten Start nötig.
|
||||
|
||||
### 3. Stream-URL im Radio eintragen
|
||||
|
||||
```
|
||||
http://<homeassistant-ip>:8000/stream
|
||||
```
|
||||
|
||||
### 4. Musik abspielen
|
||||
|
||||
Wähle in der Spotify-App **„Busch-Radio"** (oder deinen konfigurierten Gerätenamen) als Abspielgerät aus.
|
||||
|
||||
---
|
||||
|
||||
## Konfigurationsoptionen
|
||||
|
||||
| Option | Standard | Beschreibung |
|
||||
|---|---|---|
|
||||
| `device_name` | `Busch-Radio` | Name des Geräts in der Spotify-App |
|
||||
| `bitrate` | `160` | Audioqualität in kbps (`96` / `128` / `160` / `192`) |
|
||||
| `stream_format` | `mp3` | Audioformat (`mp3` oder `aac`) |
|
||||
| `stream_port` | `8000` | HTTP-Port des Icecast-Streams |
|
||||
| `stream_mount` | `/stream` | URL-Pfad des Streams |
|
||||
| `icecast_password` | – | Internes Icecast-Passwort (bitte ändern) |
|
||||
|
||||
---
|
||||
|
||||
## Nützliche URLs
|
||||
|
||||
| URL | Funktion |
|
||||
|---|---|
|
||||
| `http://<ha-ip>:5589` | Einrichtungs-Assistent (OAuth) |
|
||||
| `http://<ha-ip>:8000/stream` | Stream-URL für das Radio |
|
||||
| `http://<ha-ip>:8000` | Icecast-Statusseite |
|
||||
|
||||
---
|
||||
|
||||
## Fehlerbehebung
|
||||
|
||||
**„Busch-Radio" erscheint nicht in der Spotify-App**
|
||||
→ Spotify-Autorisierung noch nicht abgeschlossen. Einrichtungs-Seite öffnen: `http://<ha-ip>:5589`
|
||||
|
||||
**Stream nicht erreichbar**
|
||||
→ Icecast-Statusseite prüfen: `http://<ha-ip>:8000`
|
||||
→ Port 8000 muss im Netzwerk erreichbar sein (`host_network: true` ist aktiviert)
|
||||
|
||||
**Stream bricht bei Pause ab**
|
||||
→ Add-on auf Version 1.0.11 oder neuer aktualisieren
|
||||
|
||||
**Songs wechseln zu schnell / keine Steuerung vom Handy**
|
||||
→ Add-on auf Version 1.0.10 oder neuer aktualisieren
|
||||
|
||||
**Neu autorisieren**
|
||||
→ Add-on stoppen → Datei `/data/librespot-cache/credentials.json` löschen → Add-on starten → Einrichtungs-Seite öffnen
|
||||
|
||||
---
|
||||
|
||||
## Technische Hinweise
|
||||
|
||||
- librespot wird beim Build aus dem Quellcode kompiliert (Rust) – der erste Build dauert ca. 10–15 Minuten
|
||||
- Der OAuth-Callback-Server bindet an `0.0.0.0`, damit er aus dem Heimnetz erreichbar ist
|
||||
- Audio wird nicht gespeichert – ausschließlich live gestreamt
|
||||
- Spotify Premium ist für Spotify Connect erforderlich
|
||||
@@ -70,6 +70,7 @@ RUN chmod +x /usr/local/bin/librespot
|
||||
|
||||
# Add-on Dateien kopieren
|
||||
COPY run.sh /run.sh
|
||||
RUN chmod a+x /run.sh
|
||||
COPY oauth_helper.py /oauth_helper.py
|
||||
RUN chmod a+x /run.sh /oauth_helper.py
|
||||
|
||||
CMD ["/run.sh"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "Busch-Radio Spotify Bridge"
|
||||
version: "1.0.10"
|
||||
version: "1.0.11"
|
||||
slug: busch_radio_spotify
|
||||
description: >
|
||||
Streamt Spotify-Musik als Internet-Radio-Stream für das Busch-Jäger Unterputz-Internetradio.
|
||||
|
||||
283
busch_radio_spotify/oauth_helper.py
Normal file
283
busch_radio_spotify/oauth_helper.py
Normal file
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OAuth-Hilfsserver für die Busch-Radio Spotify Bridge.
|
||||
|
||||
Stellt unter http://<ha-ip>:5589 eine Hilfsseite bereit,
|
||||
die den einmaligen Spotify-Autorisierungsprozess vereinfacht.
|
||||
|
||||
Endpoints:
|
||||
GET / -> HTML-Hilfsseite
|
||||
GET /auth-url -> JSON: {"url": "https://accounts.spotify.com/..."}
|
||||
GET /status -> JSON: {"authed": true/false}
|
||||
"""
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.parse
|
||||
|
||||
HA_IP = sys.argv[1] if len(sys.argv) > 1 else "homeassistant.local"
|
||||
AUTH_URL_FILE = "/tmp/busch-radio/auth_url.txt"
|
||||
CRED_FILE = "/data/librespot-cache/credentials.json"
|
||||
|
||||
HTML = f"""<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Busch-Radio – Spotify-Anmeldung</title>
|
||||
<style>
|
||||
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
min-height: 100vh;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}}
|
||||
.card {{
|
||||
background: #16213e;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
max-width: 620px;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #0f3460;
|
||||
}}
|
||||
h1 {{ color: #1db954; font-size: 1.4em; margin-bottom: 6px; }}
|
||||
h2 {{
|
||||
color: #8a9bb0;
|
||||
font-size: 0.8em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 14px;
|
||||
}}
|
||||
.step {{
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-start;
|
||||
margin: 10px 0;
|
||||
padding: 14px;
|
||||
background: #0f3460;
|
||||
border-radius: 8px;
|
||||
}}
|
||||
.num {{
|
||||
background: #1db954;
|
||||
color: #000;
|
||||
border-radius: 50%;
|
||||
width: 26px; height: 26px;
|
||||
min-width: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.85em;
|
||||
}}
|
||||
.body {{ flex: 1; }}
|
||||
p {{ margin: 5px 0; line-height: 1.55; font-size: 0.93em; }}
|
||||
code {{
|
||||
background: #1a1a2e;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: #f0a500;
|
||||
font-size: 0.88em;
|
||||
word-break: break-all;
|
||||
}}
|
||||
.btn {{
|
||||
display: inline-block;
|
||||
background: #1db954;
|
||||
color: #000 !important;
|
||||
padding: 9px 20px;
|
||||
border-radius: 20px;
|
||||
font-weight: 700;
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
margin-top: 10px;
|
||||
}}
|
||||
.btn:hover {{ background: #17a349; }}
|
||||
input[type=text] {{
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #264070;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
font-size: 0.88em;
|
||||
margin-top: 8px;
|
||||
}}
|
||||
.spinner {{
|
||||
display: inline-block;
|
||||
width: 14px; height: 14px;
|
||||
border: 2px solid #333;
|
||||
border-top-color: #1db954;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}}
|
||||
@keyframes spin {{ to {{ transform: rotate(360deg); }} }}
|
||||
.ok {{ color: #1db954; }}
|
||||
.warn {{ color: #f0a500; }}
|
||||
.err {{ color: #e05050; font-size: 0.85em; margin-top: 6px; display: none; }}
|
||||
.hint {{ color: #8a9bb0; font-size: 0.83em; margin-top: 5px; }}
|
||||
.hidden {{ display: none !important; }}
|
||||
.authed-banner {{
|
||||
background: #0b3d1f;
|
||||
border: 1px solid #1db954;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>🎵 Busch-Radio – Spotify-Anmeldung</h1>
|
||||
<p class="hint">Einmaliger Einrichtungs-Assistent • Credentials werden dauerhaft gespeichert.</p>
|
||||
<div class="authed-banner" id="authed-banner">
|
||||
<span class="ok">✓ Bereits autorisiert – keine erneute Anmeldung nötig.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Schritt 1 – Spotify-Anmeldung starten</h2>
|
||||
<div class="step">
|
||||
<div class="num">1</div>
|
||||
<div class="body">
|
||||
<p>Klicke auf den Link, um die Spotify-Autorisierung zu öffnen.</p>
|
||||
<div id="auth-waiting">
|
||||
<p class="warn">Warte auf librespot…<span class="spinner"></span></p>
|
||||
</div>
|
||||
<div id="auth-ready" class="hidden">
|
||||
<a id="auth-link" href="#" target="_blank" class="btn">Mit Spotify anmelden →</a>
|
||||
</div>
|
||||
<p class="hint">Du benötigst ein Spotify-Premium-Konto.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="num">2</div>
|
||||
<div class="body">
|
||||
<p>Melde dich mit deinem Spotify-Konto an und bestätige die Berechtigungsanfrage.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Schritt 2 – Weiterleitung abschließen</h2>
|
||||
<div class="step">
|
||||
<div class="num">3</div>
|
||||
<div class="body">
|
||||
<p>Nach der Anmeldung leitet Spotify auf eine Seite weiter, die nicht erreichbar ist:</p>
|
||||
<p><code>http://127.0.0.1:5588/login?code=…</code></p>
|
||||
<p>Kopiere diese URL vollständig aus der Adressleiste.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="num">4</div>
|
||||
<div class="body">
|
||||
<p>Füge die kopierte URL hier ein und klicke auf „Weiter“:</p>
|
||||
<input type="text" id="url-input" placeholder="http://127.0.0.1:5588/login?code=...">
|
||||
<p id="url-err" class="err">Bitte eine gültige URL einfügen (beginnt mit <code>http://127.0.0.1:5588</code>).</p>
|
||||
<button class="btn" onclick="fixUrl()">Weiter →</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="num">5</div>
|
||||
<div class="body">
|
||||
<p>Im neu geöffneten Tab erscheint: <span class="ok">Autorisierung erfolgreich!</span></p>
|
||||
<p>Das Add-on startet danach automatisch durch. Diese Seite kann geschlossen werden.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const HA_IP = "{HA_IP}";
|
||||
|
||||
async function checkAuthUrl() {{
|
||||
try {{
|
||||
const r = await fetch('/auth-url');
|
||||
const d = await r.json();
|
||||
if (d.url) {{
|
||||
document.getElementById('auth-waiting').classList.add('hidden');
|
||||
document.getElementById('auth-link').href = d.url;
|
||||
document.getElementById('auth-ready').classList.remove('hidden');
|
||||
}} else {{
|
||||
setTimeout(checkAuthUrl, 2000);
|
||||
}}
|
||||
}} catch(e) {{
|
||||
setTimeout(checkAuthUrl, 2000);
|
||||
}}
|
||||
}}
|
||||
|
||||
async function checkAuthed() {{
|
||||
try {{
|
||||
const r = await fetch('/status');
|
||||
const d = await r.json();
|
||||
if (d.authed) {{
|
||||
document.getElementById('authed-banner').style.display = 'block';
|
||||
}}
|
||||
}} catch(e) {{}}
|
||||
}}
|
||||
|
||||
function fixUrl() {{
|
||||
const raw = document.getElementById('url-input').value.trim();
|
||||
const err = document.getElementById('url-err');
|
||||
if (!raw.startsWith('http://127.0.0.1:5588')) {{
|
||||
err.style.display = 'block';
|
||||
return;
|
||||
}}
|
||||
err.style.display = 'none';
|
||||
window.open(raw.replace('127.0.0.1', HA_IP), '_blank');
|
||||
}}
|
||||
|
||||
document.getElementById('url-input').addEventListener('keydown', function(e) {{
|
||||
if (e.key === 'Enter') fixUrl();
|
||||
}});
|
||||
|
||||
checkAuthUrl();
|
||||
checkAuthed();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class Handler(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
def do_GET(self):
|
||||
path = urllib.parse.urlparse(self.path).path
|
||||
|
||||
if path == "/auth-url":
|
||||
url = None
|
||||
if os.path.exists(AUTH_URL_FILE):
|
||||
with open(AUTH_URL_FILE) as f:
|
||||
url = f.read().strip() or None
|
||||
body = json.dumps({"url": url}).encode()
|
||||
self._respond(200, "application/json", body)
|
||||
|
||||
elif path == "/status":
|
||||
body = json.dumps({"authed": os.path.exists(CRED_FILE)}).encode()
|
||||
self._respond(200, "application/json", body)
|
||||
|
||||
else:
|
||||
body = HTML.encode()
|
||||
self._respond(200, "text/html; charset=utf-8", body)
|
||||
|
||||
def _respond(self, code, content_type, body):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", content_type)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = http.server.HTTPServer(("0.0.0.0", 5589), Handler)
|
||||
server.serve_forever()
|
||||
@@ -3,9 +3,11 @@
|
||||
# Busch-Radio Spotify Bridge – Startup-Script
|
||||
#
|
||||
# Startet:
|
||||
# 1. Icecast2 – HTTP-Stream-Server
|
||||
# 2. librespot – Virtueller Spotify Connect-Empfänger (gibt PCM auf stdout aus)
|
||||
# 3. ffmpeg – Kodiert PCM → MP3 und schickt es als Icecast-Quelle
|
||||
# 1. Icecast2 – HTTP-Stream-Server
|
||||
# 2. OAuth-Hilfe – Einrichtungs-Assistent auf Port 5589
|
||||
# 3. librespot – Virtueller Spotify Connect-Empfänger (PCM auf stdout)
|
||||
# 4. Silence-Injektor– Fügt Stille ein wenn Spotify pausiert (hält Stream aufrecht)
|
||||
# 5. ffmpeg – Kodiert PCM → MP3/AAC und schickt es als Icecast-Quelle
|
||||
#
|
||||
# Bei Absturz wird die komplette Pipeline nach 5 Sekunden neu gestartet.
|
||||
# ==============================================================================
|
||||
@@ -32,7 +34,7 @@ else
|
||||
fi
|
||||
|
||||
bashio::log.info "╔══════════════════════════════════════════════╗"
|
||||
bashio::log.info "║ Busch-Radio Spotify Bridge v1.0.10 ║"
|
||||
bashio::log.info "║ Busch-Radio Spotify Bridge v1.0.11 ║"
|
||||
bashio::log.info "╚══════════════════════════════════════════════╝"
|
||||
bashio::log.info "Gerätename : ${DEVICE_NAME}"
|
||||
bashio::log.info "Bitrate : ${BITRATE} kbps"
|
||||
@@ -152,7 +154,7 @@ bashio::log.info "Icecast läuft (PID: ${ICECAST_PID})"
|
||||
CREDENTIAL_CACHE="/data/librespot-cache"
|
||||
mkdir -p "${CREDENTIAL_CACHE}"
|
||||
|
||||
# ── IP-Adresse des Hosts ermitteln (für OAuth-Anleitung) ─────────────────────
|
||||
# ── IP-Adresse des Hosts ermitteln ───────────────────────────────────────────
|
||||
HA_IP=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
|
||||
if [ -z "${HA_IP}" ]; then
|
||||
HA_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
@@ -161,11 +163,14 @@ if [ -z "${HA_IP}" ]; then
|
||||
HA_IP="<homeassistant-ip>"
|
||||
fi
|
||||
|
||||
# ── OAuth-Hilfsserver starten ─────────────────────────────────────────────────
|
||||
# Stellt unter http://<ha-ip>:5589 einen Einrichtungs-Assistenten bereit,
|
||||
# der den Spotify-OAuth-Prozess Schritt für Schritt erklärt.
|
||||
python3 /oauth_helper.py "${HA_IP}" &
|
||||
OAUTH_HELPER_PID=$!
|
||||
bashio::log.info "OAuth-Hilfsseite: http://${HA_IP}:5589"
|
||||
|
||||
# ── Librespot-Argumente zusammenstellen ───────────────────────────────────────
|
||||
# Zeroconf/mDNS funktioniert nicht zuverlässig in Containern, da Port 5353
|
||||
# vom Host (systemd-resolved/avahi) bereits belegt ist.
|
||||
# Stattdessen: OAuth-Authentifizierung. Das Gerät registriert sich bei
|
||||
# Spotifys Servern und erscheint in der App ohne lokales mDNS.
|
||||
LIBRESPOT_ARGS=(
|
||||
"--name" "${DEVICE_NAME}"
|
||||
"--bitrate" "${BITRATE}"
|
||||
@@ -185,24 +190,10 @@ else
|
||||
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bashio::log.info "ERSTER START – Spotify-Autorisierung erforderlich!"
|
||||
bashio::log.info ""
|
||||
bashio::log.info "1) Gleich erscheint im Log eine Zeile:"
|
||||
bashio::log.info " Browse to: https://accounts.spotify.com/authorize?..."
|
||||
bashio::log.info " Kopiere diese URL vollständig und öffne sie im Browser."
|
||||
bashio::log.info "Öffne die Einrichtungs-Seite im Browser:"
|
||||
bashio::log.info " http://${HA_IP}:5589"
|
||||
bashio::log.info ""
|
||||
bashio::log.info "2) Melde dich mit deinem Spotify-Konto an"
|
||||
bashio::log.info " und erteile die Berechtigung."
|
||||
bashio::log.info ""
|
||||
bashio::log.info "3) Dein Browser wird danach weitergeleitet auf:"
|
||||
bashio::log.info " http://127.0.0.1:5588/login?code=..."
|
||||
bashio::log.info " Diese Seite schlägt fehl – das ist normal."
|
||||
bashio::log.info ""
|
||||
bashio::log.info "4) WICHTIG: Ersetze in der Adressleiste"
|
||||
bashio::log.info " '127.0.0.1' durch '${HA_IP}'"
|
||||
bashio::log.info " → http://${HA_IP}:5588/login?code=..."
|
||||
bashio::log.info " und drücke Enter."
|
||||
bashio::log.info ""
|
||||
bashio::log.info "5) Bei Erfolg siehst du: 'Autorisierung erfolgreich!'"
|
||||
bashio::log.info " Das Add-on startet danach automatisch."
|
||||
bashio::log.info "Der Assistent führt dich durch die Anmeldung."
|
||||
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
fi
|
||||
|
||||
@@ -215,7 +206,7 @@ bashio::log.info "Stream-URL für das Busch-Jäger Radio:"
|
||||
bashio::log.info " http://${HA_IP}:${STREAM_PORT}${STREAM_MOUNT}"
|
||||
bashio::log.info ""
|
||||
bashio::log.info "Icecast-Statusseite:"
|
||||
bashio::log.info " http://<homeassistant-ip>:${STREAM_PORT}"
|
||||
bashio::log.info " http://${HA_IP}:${STREAM_PORT}"
|
||||
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
bashio::log.info "Wähle '${DEVICE_NAME}' in der Spotify-App als Abspielgerät."
|
||||
bashio::log.info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
@@ -223,19 +214,46 @@ bashio::log.info "━━━━━━━━━━━━━━━━━━━━
|
||||
# ── Aufräumen bei SIGTERM ─────────────────────────────────────────────────────
|
||||
cleanup() {
|
||||
bashio::log.info "Add-on wird beendet..."
|
||||
kill "${ICECAST_PID}" 2>/dev/null || true
|
||||
kill "${ICECAST_PID}" "${OAUTH_HELPER_PID}" 2>/dev/null || true
|
||||
exit 0
|
||||
}
|
||||
trap cleanup SIGTERM SIGINT
|
||||
|
||||
# ── Silence-Injektor ─────────────────────────────────────────────────────────
|
||||
# Sitzt zwischen librespot und ffmpeg.
|
||||
# Wenn Spotify pausiert, hört librespot auf zu schreiben. Ohne Injektor würde
|
||||
# ffmpeg/Icecast den Stream nach einigen Sekunden Stille trennen.
|
||||
# Der Injektor füllt die Pause mit Stille-Bytes, damit der Stream bestehen bleibt.
|
||||
SILENCE_INJECTOR='
|
||||
import sys, select, os
|
||||
CHUNK = 8820 # 50 ms @ 44100 Hz, S16LE, Stereo (44100*2*2/20)
|
||||
silence = bytes(CHUNK)
|
||||
fd = sys.stdin.buffer.fileno()
|
||||
while True:
|
||||
r, _, _ = select.select([fd], [], [], 0.05)
|
||||
if r:
|
||||
data = os.read(fd, CHUNK)
|
||||
if not data:
|
||||
break
|
||||
sys.stdout.buffer.write(data)
|
||||
else:
|
||||
sys.stdout.buffer.write(silence)
|
||||
sys.stdout.buffer.flush()
|
||||
'
|
||||
|
||||
# ── Haupt-Schleife: Pipeline starten und bei Absturz neu starten ──────────────
|
||||
#
|
||||
# Signalfluss:
|
||||
# librespot (Spotify Connect) → stdout (raw PCM 44100Hz/S16LE/Stereo)
|
||||
# ↓
|
||||
# ffmpeg (PCM → MP3 Encoding) → Icecast (HTTP-Stream)
|
||||
# ↓
|
||||
# Busch-Jäger Radio (HTTP-Client)
|
||||
# silence-injector (Python) → leitet durch; bei Pause: sendet Stille
|
||||
# ↓
|
||||
# ffmpeg -re (Echtzeit) → PCM → MP3/AAC Encoding → Icecast
|
||||
# ↓
|
||||
# Busch-Jäger Radio (HTTP-Client)
|
||||
#
|
||||
# -re: Liest den Input in Echtzeit – verhindert zu schnelles Vorspulen
|
||||
# und gibt librespot korrekte Positions-Rückmeldung (Pause/Skip).
|
||||
#
|
||||
# Wenn entweder librespot oder ffmpeg abstürzt, wird die komplette Pipeline
|
||||
# nach RESTART_DELAY Sekunden neu gestartet.
|
||||
@@ -246,7 +264,20 @@ while true; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
bashio::log.info "Pipeline-Start (Versuch ${ATTEMPT})..."
|
||||
|
||||
# Librespot stderr: Zeilen werden an HA-Log weitergeleitet UND auf
|
||||
# "Browse to:"-URL geprüft → wird in Datei für OAuth-Hilfsserver gespeichert.
|
||||
librespot "${LIBRESPOT_ARGS[@]}" \
|
||||
2> >(while IFS= read -r line; do
|
||||
echo "$line" >&2
|
||||
case "$line" in
|
||||
*"Browse to:"*)
|
||||
echo "$line" | sed 's/.*Browse to: //' | tr -d '\r\n ' \
|
||||
> /tmp/busch-radio/auth_url.txt
|
||||
bashio::log.info "Spotify-Anmeldung: http://${HA_IP}:5589 öffnen"
|
||||
;;
|
||||
esac
|
||||
done) \
|
||||
| python3 -c "${SILENCE_INJECTOR}" \
|
||||
| ffmpeg \
|
||||
-re \
|
||||
-hide_banner \
|
||||
@@ -259,6 +290,9 @@ while true; do
|
||||
"${ICECAST_SOURCE_URL}" \
|
||||
|| true
|
||||
|
||||
# Auth-URL-Datei leeren damit die Hilfsseite beim Neustart wieder wartet
|
||||
rm -f /tmp/busch-radio/auth_url.txt
|
||||
|
||||
bashio::log.warning "Pipeline beendet. Neustart in ${RESTART_DELAY}s..."
|
||||
sleep "${RESTART_DELAY}"
|
||||
done
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "busch_radio_spotify",
|
||||
"name": "Busch-Radio Spotify Bridge",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.11",
|
||||
"documentation": "https://gitea.bitfire.work/retr0/Busch-Radio-Spotify",
|
||||
"issue_tracker": "https://gitea.bitfire.work/retr0/Busch-Radio-Spotify/issues",
|
||||
"requirements": [],
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# ================================================================
|
||||
# Beispielkonfiguration: Busch-Radio Spotify Bridge Add-on
|
||||
# ================================================================
|
||||
#
|
||||
# Diese Optionen werden in der HA Add-on Oberfläche eingetragen
|
||||
# (Einstellungen → Add-ons → Busch-Radio Spotify Bridge → Konfiguration).
|
||||
#
|
||||
# Alternativ direkt in der YAML-Ansicht.
|
||||
# ================================================================
|
||||
|
||||
# Name des virtuellen Spotify-Connect-Geräts.
|
||||
# Erscheint in der Spotify-App unter "Geräte".
|
||||
device_name: "Busch-Radio"
|
||||
|
||||
# Audio-Bitrate in kbps. Höhere Werte = bessere Qualität, mehr Bandbreite.
|
||||
# Erlaubte Werte: 96, 160, 320
|
||||
bitrate: 320
|
||||
|
||||
# Port auf dem der Icecast-Stream lauscht.
|
||||
# Dieser Port muss im Add-on unter "Netzwerk" freigegeben sein.
|
||||
stream_port: 8000
|
||||
|
||||
# Pfad des Streams. Wird an die URL angehängt.
|
||||
# Empfehlung: mit .mp3 enden für maximale Kompatibilität.
|
||||
stream_mount: "/stream.mp3"
|
||||
|
||||
# Passwort für Icecast (intern verwendet, nicht für Hörer sichtbar).
|
||||
# Ändere diesen Wert aus Sicherheitsgründen!
|
||||
icecast_password: "mein-sicheres-passwort"
|
||||
|
||||
# Optional: Spotify Premium Zugangsdaten.
|
||||
# Wenn leer, wird Zeroconf/mDNS-Erkennung verwendet (empfohlen).
|
||||
# Das Gerät wird dann automatisch in der Spotify-App im lokalen Netz gefunden.
|
||||
username: ""
|
||||
password: ""
|
||||
|
||||
# ================================================================
|
||||
# Stream-URL für das Busch-Jäger Radio:
|
||||
# http://<homeassistant-ip>:8000/stream.mp3
|
||||
#
|
||||
# Beispiel:
|
||||
# http://192.168.1.100:8000/stream.mp3
|
||||
# ================================================================
|
||||
Reference in New Issue
Block a user