This content has been generated by GLM 5.1 AI model
Bon, imagine le truc : t'as un Raspberry Pi branché à des enceintes dans ton salon. Il fait tourner mpv parce que t'es pas un animal — t'utilises un lecteur média qui en vaut la peine. Mais à chaque fois que tu veux passer un morceau, changer le volume ou fouiller dans ta bibliothèque, tu dois te connecter en SSH à la bestiole et taper des commandes comme une espèce de... personne qui tape des commandes... dans un terminal.
Ouais, moi aussi. Et après la 47ème fois que j'ai tapé mpv --no-video /mnt/music/UnAlbum/chanson.flac pendant que mes pâtes cramaien, j'ai décidé que ça suffisait.
mpv-web-control c'est une télécommande web pour mpv. Tu ouvres un navigateur sur ton téléphone (ou n'importe quel appareil sur ton réseau local), et t'as une interface complète de lecteur de musique — naviguer dans la bibliothèque, mettre en queue des fichiers et des dossiers, play/pause/suivant/seek/volume, sauvegarder et charger des playlists. Le tout connecté à mpv qui tourne sur ton Pi.
Pas d'app native. Pas de service cloud. Pas d'abonnement mensuel pour contrôler ta propre musique sur tes propres enceintes. Juste une page web sur ton réseau local.
Ce que ça fait
La liste de fonctionnalités, c'est ce que tu t'attends à trouver dans une télécommande de lecteur musique :
- Naviguer dans ta bibliothèque musicale par dossier (pas de système de tags, juste des fichiers)
- Queue des fichiers individuels ou des dossiers entiers en mode récursif
- Contrôles de lecture — play, pause, suivant, précédent, stop, seek, volume
- Gestion de la file — voir ce qui joue, sauter à n'importe quel morceau, vider la file
- Playlists — sauvegarder la file actuelle en JSON, la recharger plus tard, la compléter, la supprimer
Pas de base de données. Les playlists sont juste des fichiers JSON sur le disque. La bibliothèque c'est tout ce qui se trouve sous ton répertoire MUSIC_ROOT. Simple, basique, et ça marche.
La stack
C'est un monorepo pnpm avec trois apps et un package de contrat partagé :
- Server — Hono sur Node.js, discute avec mpv via JSON IPC sur un socket Unix
- Client — SPA React avec TanStack Router et TanStack Query, composants shadcn/ui
- Contract — Types TypeScript partagés entre le serveur et le client
- CLI — Binaire
mpv-web-controlavec les commandes install/uninstall/start/package
Le serveur lance mpv comme processus fils avec --no-video --idle=yes et communique à travers un socket Unix en utilisant le protocole JSON IPC de mpv. Chaque commande passe par une validation propre avec des schémas Zod, et la protection contre le path traversal garantit que personne peut s'échapper du répertoire MUSIC_ROOT (ça tourne sur ton LAN, mais quand même — je suis pas ça inconscient).
typescriptfunction resolveLibraryPath(inputPath: string): string { if (isAbsolute(inputPath)) { throw new Error('Absolute paths are not allowed') } const absolutePath = resolve(config.musicRoot, inputPath) const relativePath = relative(config.musicRoot, absolutePath) if (relativePath.startsWith('..') || isAbsolute(relativePath)) { throw new Error('Path escapes MUSIC_ROOT') } return absolutePath }
Le client poll le serveur toutes les secondes pour récupérer le statut du lecteur. TanStack Query gère le cache et l'invalidation — quand tu mets un fichier en queue, la query de statut est invalidée pour que l'UI se mette à jour de suite. Assez optimiste pour être réactif, pas assez pour te mentir.
La communication avec mpv
Ça c'était la partie intéressante. La classe MpvService gère tout le cycle de vie :
- Lance mpv en tant que processus fils
- Attend que le socket IPC apparaisse (poll pendant 5 secondes)
- Se connecte au socket
- Envoie les commandes en JSON avec des IDs de requête
- Fait correspondre les réponses aux requêtes en attente
Chaque commande reçoit un ID unique et un timeout de 5 secondes. Le handler de données du socket bufferise ce qui arrive, split sur les retours à la ligne, parse le JSON, et route les réponses vers leurs promesses en attente. Du classique requête-réponse sur un flux texte, rien de fancy mais ça fait le taf.
typescriptasync status(): Promise<PlayerStatus> { await this.ensureStarted() const [paused, position, duration, volume, playlist, playlistPos] = await Promise.all([ this.safeProperty<boolean>('pause'), this.safeProperty<number>('time-pos'), this.safeProperty<number>('duration'), this.safeProperty<number>('volume'), this.safeProperty<MpvPlaylistItem[]>('playlist'), this.safeProperty<number>('playlist-pos'), ]) // ... construit l'objet de statut }
Si mpv crash ou que le socket meurt, le service nettoie et redémarre à la prochaine requête. Parce que le hardware fait des siennes, surtout sur un Pi ^^
Installation (la voie facile)
bashnpm install -g mpv-web-control sudo mpv-web-control install --user $USER --music-root /mnt/music --port 3000
C'est tout. La commande d'install écrit la config dans /etc/mpv-web-control/env, crée une unit systemd, l'active, et démarre le service. Ta télécommande musicale est maintenant dispo sur http://ton-pi:3000 depuis n'importe quel appareil de ton réseau.
bashsudo systemctl status mpv-web-control sudo journalctl -u mpv-web-control -f
Pour désinstaller, sudo mpv-web-control uninstall arrête le service, le désactive, et te demande quoi faire de la config et des données. Il supprime pas ton utilisateur Linux (de rien).
Variables d'environnement
Tout est configurable :
| Variable | Défaut | Ce que ça fait |
|----------|---------|-------------|
| HOST | 0.0.0.0 | Adresse de bind pour l'accès LAN |
| PORT | 3000 | Port du serveur |
| MUSIC_ROOT | répertoire courant | Où vit ta musique |
| PLAYLISTS_DIR | .mpv-web-control/playlists | Où sont sauvegardées les playlists |
| MPV_SOCKET_PATH | /tmp/mpv-web-control.sock | Chemin du socket IPC mpv |
| MPV_BIN | mpv | Chemin du binaire mpv |
| MAX_FOLDER_ITEMS | 5000 | Plafond de sécurité pour la mise en queue récursive |
Le plafond MAX_FOLDER_ITEMS existe parce que j'ai essayé de mettre /mnt/music en queue une fois et... disons juste que mpv était pas ravi de recevoir 47 000 fichiers. La sécurité d'abord, les enfants.
Le script de release
Ce projet a même un script de release parce que visiblement j'aime écrire du bash presque autant que du TypeScript :
bashbash scripts/release.sh 1.0.0 --dry-run # prévisualiser ce qui va se passer bash scripts/release.sh 1.0.0 # le faire pour de vrai
Il valide la version, lance le typecheck et le build, bump tous les package.json, commit, tag, push, crée une GitHub Release avec un tarball, et publie sur npm. Tout d'un coup. Les flags --no-gh, --no-npm et --dry-run te laissent contrôler ce qui tourne.
Notes de sécurité
C'est conçu pour un usage LAN de confiance. La protection contre le path traversal est en place (les chemins absolus et les tentatives d'évasion en ../ se font jeter), mais y'a pas d'authentification. N'exposez PAS ça directement sur internet. Ta musique, c'est entre toi et tes enceintes, là où elle doit être.
Pourquoi j'ai fait ça
Parce que j'en avais marre de me SSH-er dans mon Pi pour changer de morceau. C'est tout. C'est la seule raison. Parfois la meilleure motivation, c'est la pure flemme.
Mais aussi ? C'est un chouette exemple de projet full-stack TypeScript avec une vraie séparation des préoccupations. Le package de contrat partagé fait que le client et le serveur sont toujours d'accord sur les types. Hono rend le backend léger et rapide. Le client React avec TanStack Query gère les updates temps temps réel proprement. Et le tout s'installe comme service systemd en une seule commande.
Parfois les meilleurs projets naissent des problèmes les plus cons ^^
{{% goodbye %}}