Musik-Streaming mit Android-App
AhoyMusic
Mein eigener Musik-Streaming-Dienst mit nativer Android-App, seit zwei Monaten mein Daily Driver.
Seit 2 Monaten mein Daily Driver
10 TRACKS · 85 MB
Good evening
Liked Songs
Nachbau der echten App mit erfundenen Titeln und ohne Ton.
Kernkompetenzen
- Mobile und Native
- Backend und APIs
- Echtzeit-Systeme
- Infrastruktur und Betrieb
- Zuverlässigkeit
- Testen und Verifizieren
Tech-Stack
- Expo
- React Native
- Reanimated
- expo-audio
- Kotlin
- Media3
- Expo Modules API
- MediaStore
- NestJS
- MongoDB
- Redis
- BullMQ
- ffmpeg
- Linux
- pm2
- nginx
- Let's Encrypt
Das Schwierigste
Der Sperrbildschirm, der beim ersten Lied hängen blieb
Was eingefroren aussieht, kann auch eine Schleife sein, die zu schnell läuft.
Die ganze Geschichte
Was man sah
Die Musik lief ganz normal weiter, aber in der Benachrichtigung auf dem Sperrbildschirm stand für immer der Titel des ersten Lieds.
Was wirklich los war
Eine Endlosschleife: Jede Benachrichtigung startete den Hintergrunddienst neu, und jeder Neustart schickte wieder eine Benachrichtigung. Android warf die Flut einfach weg.
Die Lösung
Der Dienst meldet sich nur noch bei einer ausdrücklichen Aktion. Bewiesen mit echten Taps auf die Benachrichtigung im Emulator.
- 846
- Tests bestanden, App 548 und API 298
- ~24k
- Zeilen Code, dazu ~10,7k Zeilen Tests
- ~1.000
- Zeilen Kotlin in 4 Dateien
- 2 Monate
- täglich im Einsatz
In einfachen Worten
Statt für ein Musik-Abo zu zahlen, läuft meine Musik auf meinem eigenen Server. Die App spielt sie online oder offline, im Hintergrund und mit Steuerung auf dem Sperrbildschirm, wie bei den großen Anbietern. Ich nutze sie jeden Tag.
Ganz einfach: um Kosten zu sparen. Und weil ein Musik-Player ein fieser Härtetest ist. Audio, Android-Lebenszyklus und Offline-Sync verzeihen nichts, und wenn etwas kaputt ist, merke ich es täglich selbst.
So funktioniert es
Schritt 1 von 5
Import
Ein BullMQ-Worker außerhalb der API verarbeitet Tracks einzeln, damit ein hängender Job keine Anfragen blockiert.
Was gebaut wurde
- Player mit Queue, Shuffle, Repeat, Hintergrundwiedergabe und Sperrbildschirm
- Natives Kotlin-Modul mit Media3-Session, das nichts dekodiert und nur die Queue aus JS spiegelt
- Offline-Modus: Downloads pro Track, Anmeldung ohne Netz, Library-Snapshots, Aufräumen halber Dateien
- Audio-Streaming mit HTTP Range (206 und 416), damit Vorspulen auch durch nginx funktioniert
- Import-Worker mit BullMQ außerhalb der API, Fortschritt pro Track, Duplikaterkennung und Dateiupload
- Getrennte Bibliotheken pro Konto, Deploy-Skript mit Vorabprüfung, nächtliche Backups mit dokumentiertem Restore
Wie die Kompetenzen eingesetzt wurden
- Mobile und Native
- Kotlin, Foreground Services, Media3-Sessions, Runtime-Permissions und Config Plugins, dazu Debugging mit adb und logcat
- Backend und APIs
- NestJS mit MongoDB ohne ORM, Redis und BullMQ, 32 HTTP-Routen
- Echtzeit-Systeme
- Range-Requests, Proxy-Buffering und asynchrone Status-Ticks, die nicht in falscher Reihenfolge ankommen dürfen
- Infrastruktur und Betrieb
- Linux, pm2, nginx, Let's Encrypt, logrotate, Disk-Guard und Backups, die vor leeren Dumps warnen
- Zuverlässigkeit
- Stille Fehlzustände werden zu klaren Ablehnungen, zum Beispiel beim Deploy mit altem Installationspfad
- Testen und Verifizieren
- 846 Unit-Tests, und den nativen Teil habe ich am echten Gerät und im Emulator geprüft
Meine Rolle
Konzept, Architektur, Führung der Agenten und Prüfung. Den Großteil des Codes hat Claude Code in meinem selbst entwickelten Agent-Workflow geschrieben.
Siehe Projekt Context Observer