VLX – Une télécommande pour VLC

16 novembre 2022 ⋅ #Projets #Elixir

Pourquoi créer une télécommande pour VLC ?

Le but de ce projet était d'avoir une télécommande pilotant VLC depuis le canapé.

Notre setup pour regarder des films à la maison consiste en un ordinateur relié à un vidéoprojecteur, une baffle bluetooth et un canapé recouvert de coussins et autres couettes d'appoint.

J'utilise VLC pour lire les vidéos, et il m'arrive d’enchaîner beaucoup d'épisodes lors de longues sessions. Cependant, à chaque changement d'épisode, VLC se remet en français sans sous-titres par défaut, or je préfère regarder mes vidéos en VO sous-titrée en français.

Et concrètement une fois calé dans le canapé avec la couette, les chips, la gourde et tout le stuff nécessaire à une bonne session de binge watching, se lever pour aller changer l'audio et les sous-titres est fort déplaisant, notamment lorsque le coussin à enfin pris la forme parfaite pour caler le dos et la nuque.

De plus je n'utilise pas les playlists de VLC, notamment parce que les épisodes suivants sont en cours de téléchargement pendant le visionnage.

J'ai donc cherché une solution pour pouvoir changer les paramètres de langue depuis le canapé :

  • Il est possible de configurer VLC pour utiliser une piste audio et de sous-titres par défaut, mais d'une part les noms des pistes sont parfois mal indiqués dans les flux des fichiers vidéo, et surtout l'ordinateur relié au vidéoproj' n'est pas le mien, je ne souhaite pas modifier ces paramètres. Mon propre ordinateur est sous Linux, et il y a de quoi écrire un livre complet à propos de Linux, l'audio et le bluetooth…
  • Il existe une télécommande web fournie avec VLC, mais son interface n'est vraiment pas ergonomique pour une utilisation sur mobile, elle ne gère pas de thème sombre (je visionne en général dans le noir), et surtout elle ne permet pas de changer les flux audio/sous-titres.
  • J'ai également trouvé une application Android pour contrôler VLC. Là j'avoue qu'elle semble assez complète, cependant elle requiert de configurer l'adresse IP de VLC (or l'ordinateur utilisé ici n'a pas d'IP fixe – encore une fois ce n'est pas le mien). Je cherchais quelque chose de plus simple.

Tout cela fait de bons prétextes pour développer une solution custom !

Il ne restait plus qu'à convaincre la propriétaire du PC de me laisser installer Elixir. Quelques sucreries et le tour était joué.

Les technos

Le but de ce projet n'était pas vraiment d'apprendre quelque chose de nouveau mais d'aller au plus rapide afin d'avoir l'outil disponible rapidement. J'ai donc choisi uniquement des technos qui me sont familières, adaptées au besoin :

  • Elixir, très efficace lorsqu'il s'agit de communiquer avec plusieurs services en même temps (ici le navigateur et VLC).
  • Phoenix, le framework web quasi-officiel pour Elixir.
  • Phoenix LiveView pour la rapidité de mise en place de l'UI : pas de bundle JS à gérer, pas de framework JS à installer et finalement aucun code JS à produire.
  • TailwindCSS, efficace et direct pour styliser une application en un rien de temps.

Pas si facile…

Ma motivation première était d'avoir une télécommande fonctionnelle le plus rapidement et le plus simplement possible.

Je m'attendais à ce que ça soit l'affaire de quelques heures mais il en a fallu au moins dix pour arriver à un résultat utilisable.

Voici les difficultés rencontrées lors de ce projet :

Ouvrir l'application sur le smartphone sans connaître l'IP du serveur

L'application s'exécute sur le PC servant de media center et exécutant VLC. Son adresse IP n'est pas fixe. Lors du développement j'ouvrais l'application sur localhost mais le but final est bien de lancer VLC, lancer le serveur et plonger immédiatement dans le canapé. Le smartphone doit être connecté en WiFi pour faire partie du réseau local mais il reste à connaître deux nombres :

http://192.168.<???>.<???>:4000/

Le serveur peut connaître son adresse IP locale en appelant :inet.getifaddrs() et en récupérant la première adresse IPv4 suivie du netmask {255, 255, 255, 0}. Pas très robuste… et cela ne fonctionne que sur un réseau utilisant ce masque. Mais c'est quelque chose qui pourrait être configuré facilement.

Ensuite, il s'agit de communiquer cette adresse au smartphone, qui ne la connaît pas encore et donc ne peut pas se connecter au serveur pour l'obtenir.

Ma solution : Au lancement, l'application génère un QR-code contenant son URL et l'affiche sur VLC en boucle toutes les neuf secondes (VLC n'affiche une image que dix secondes). On peut donc scanner le QR-code géant projeté sur le mur et de lancer un premier film. À partir de là, voyant qu'un film est en cours, l'application n'affichera plus de QR-Code si elle crashe et redémarre. Il serait dommage d'interrompre la lecture pour ça !

Les APIs de VLC sont limitées

J'ai d'abord cherché un moyen de communiquer avec VLC via HTTP. Cependant la documentation était assez pauvre et l'API disponible ne permettait pas de faire ce que je souhaitais.

VLC est livré avec une ligne de commande accessible via TCP, appelée interface telnet, ma première version utilisait donc un client communiquant avec elle.

J'ai implémenté le handshake qui ressemble à ceci :

VLC media player 3.0.16 Vetinari
Password:
Welcome, Master

J'ai ensuite implémenté la récupération des pistes audio, en envoyant la commande atrack, qui donne cela :

+----[ audio-es ]
| -1 - Désactiver
| 1 - Piste 1 - [Français]
| 2 - Piste 2 - [Anglais] *
+----[ end of audio-es ]

Notez le header +----[ audio-es ] et l'astérisque marquant la piste actuellement sélectionnée.

Malheureusement cette interface est vraiment conçue pour fonctionner avec telnet, suite à chaque commande elle envoie un prompt ( >) et quelques \r\n gratuits.

J'ai fait l'erreur de « consommer » le prompt avant d'envoyer une commande. Le problème est que quand aucun film n'est chargé, au lancement de tout le setup, la commande atrack ne renvoie ni header ni footer, et l'application plantait.

J'ai donc appris qu'il valait mieux consommer le prompt après avoir lancé une commande. Cependant cela restait hasardeux au regard de tout le texte qui peut être renvoyé par VLC.

En parallèle, j'ai continué à chercher des docs sur l'interface HTTP et j'ai trouvé une autre version, bien cachée, contenant d'autres commandes qui me permettraient enfin de faire tout ce dont j'avais besoin. J'ai alors décidé de jeter le client telnet et d'utiliser HTTP.

Un problème cependant, l'objet JSON que l'on peut récupérer décrivant le statut actuel du player contient des clés différentes selon la langue de l'application. La liste des flux contient des clés "Flux 1", "Flux 2", etc. pour cette version en français. La version anglaise renvoie "Stream 1" il me semble.

Ça n'est gênant car là aussi j'avais besoin d'un outil qui fonctionne sur mon setup personnel. Plus ennuyeux, l'objet statut n'indique pas quels sont les flux actuellement sélectionnés, contrairement à la version telnet qui les indique via une astérisque. Impossible alors d'indiquer les flux actuels dans la GUI de la télécommande.

J'ai décidé d'ignorer ce problème parce que la vidéo elle-même indique ces flux : il suffit de regarder et d'écouter le film. Ça reste cependant dommage, et la télécommande se contente de mettre en évidence les derniers flux sur lesquels on a cliqué. Mais si quelqu'un d'autre change les flux en utilisant directement VLC sur le PC l'état de ma GUI se retrouve donc désynchronisée.

Premature optimization kicks in

Pour garder l'application la plus simple possible, j'aurais dû garder le poll du statut de VLC et de la liste de médias directement dans le process LiveView.

Phoenix LiveView est une technologie qui gère la vue côté serveur, un peu comme une session intelligente qui permet de communiquer avec l'application.

On aurait donc un process Elixir dédié à l'utilisateur permettant de controller VLC et de lire le disque dur. Mais cela me paraissait incorrect. Imaginons que 1 000 personnes se décident à investir mon appartement et à ouvrir la télécommande sur leur smartphone en même temps : cela donnerait un polling démesuré de VLC et du disque, pour rien.

J'ai plutôt implémenté deux process pour rafraîchir régulièrement la liste de médias et le statut de VLC qui publient le nouvel état en cas de changement sur le système pubsub de Phoenix. Chaque process LiveView s'abonne aux topics correspondants et reçoit les états quand ils changent.

Rétrospectivement c'était une perte de temps vu que je l'utilise tout seul même si l'application étant techniquement utilisable à plusieurs.

Conclusion

Le projet est actuellement à l'état de proof of concept. Mais voici deux pistes d'amélioration possibles:

  • La prochaine étape serait de fournir un bundle pour qu'une personne non initiée puisse lancer le projet. Actuellement j'utilise un script bat sur Windows, qui doit être adapté à la machine hôte.
  • LiveView n'est pas vraiment adapté au projet : en général on lance un film et on pose le téléphone pendant longtemps, ce qui lui laisse le temps de se mettre en veille et de couper la connexion avec le serveur. Donc, à chaque fois qu'on réouvre la page, le serveur doit regénérer la page complètement. Je pense qu'une application JS client-side serait plus adaptée pour le frontend.

Si vous souhaitez jeter un œil, le code est sur Github.