Coin web de Frédéric Péters

Stamina, nouveau retour

21 mai 2020, 10:55

Avec les circonstances tout le monde à la radio s’est débrouillé pour continuer à produire des émissions, en préenregistrant, en direct de chez soi, voire en direct de plusieurs chez soi (lien vers le post mumble). Et chacun·e en totale autonomie, en utilisant panikdb. C’était à la base simplement le backoffice pour la publication vers le site web, permettre à tout le monde de mettre ses podcasts en lignes, puis ça a été étendu pour permettre la gestion des plages de musique continue (2017), puis pour permettre d’ajouter la diffusion à l’antenne des préenregistrés (2018), puis pour permettre de programmer la diffusion du stream d’un studio extérieur (2020).

Toutes ces tâches, il s’agit en fait de faire interface vers « soma », le logiciel en charge de la diffusion des fichiers en temps et en heure; c’est un vieux logiciel libre récupéré d’un centre social italien l’été 2005, dont les développements ont été abandonnés deux ou trois ans après. C’est vieux, il y a un daemon écrit en C, avec son propre protocole réseau, il y une interface GTK elle aussi écrite en C, il y a des bindings en Python et PHP. Il y a aussi des bouts d'italien dans l’UI incomplètement traduite en anglais.

Ça tourne depuis bientôt 15 ans. Il m’est arrivé de vouloir réécrire ça mais ça ne s’est pas concrétisé. Il y a eu pas mal de temps passé ailleurs. (mais l’idée de nom est restée)

Ça ne s’est pas concrétisé sauf qu’à faire le point il y a quelques mois je me suis rendu compte que tout passait désormais par panikdb, ou presque, il manquait encore la possibilité de définir les jingles d’introduction, ou les jingles s’insérant dans les plages musicales, ou la gestion d’une récurrence dans la diffusion de steams extérieurs, tout ça (pour ainsi dire) en place désormais.

Pour motiver ça, il fallait le déclic qui me ferait considérer à nouveau la gestion en tant que telle de la diffusion et ça a finalement été l’envie de regarder aux nouvelles possibilités asynchrones dans Python.

Et là quel bonheur, une fois dépassés les exemples trop courts et les injonctions à trop faire attention à ne rien bloquer, ça se révèle une joie à coder, à laisser toute la gestion processus/threads/peu importe au langage, à presque continuer à écrire du code comme avant, à simplement le saupoudrer de async/await.

Pour avoir un programme qui est à sa base :

# tâche de fond qui regarde si jamais il n’y a pas eu de soudaine
# modif dans les horaires
self.recompute_slots_task = asyncio.create_task(self.recompute_slots_loop())
while not self.quit:
    # tâche qui va lancer la lecture d’un son
    self.play_task = asyncio.create_task(self.play(self.slot))
    try:
        await self.play_task  # tranquille ça va tourner
    except asyncio.CancelledError as exc:
        # bah tiens, la tâche recompute_slots_task a du détecter
        # un changement important
        if self.player and self.player.returncode is None:  # not finished
            self.player.kill()

C’est-à-dire simplement tourner sans fin et lire un fichier après l’autre, le trick étant de pouvoir être interrompu, si jamais.

Cette lecture des fichiers qui profite d’ailleurs aussi :

async def player_process(self, cmd, timeout=None):
    self.player = await asyncio.create_subprocess_shell(
            cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE)
    if timeout is None:
        await self.player.communicate()
    else:
        try:
            await asyncio.wait_for(self.player.communicate(), timeout=timeout)
        except asyncio.TimeoutError:
            pass

Parce que c'est vraiment super de pouvoir ainsi lancer la lecture d’un stream extérieur en lui disant « dans une heure c’est terminé », avec juste asyncio.wait_for.

Tout ça permet rapidement de laisser ces questions de côté, ce qui permet de se concentrer ainsi sur les choses qui comptent, genre la création des playlists pour les zones de musique continue, et avoir du temps pour se faire plaisir là, à calculer des playlists dont les morceaux terminent pile au bon moment, qui peuvent être recalculées si jamais une diffusion prioritaire doit venir s’insérer.

Et ajouter une mini-couche de compatibilité pour reprendre quelques bouts du protocole réseau de soma, ça va se jouer tout seul aussi, façon :

server = await asyncio.start_server(self.handle_connection, '127.0.0.1', 12521)

Tout ça ne tourne pour de vrai pas encore, pour une part parce qu’il reste encore des choses à faire dans le code, pour une autre part parce qu’il reste des questions à trancher sur certaines zones de la grille horaire de la radio, mais c’était vraiment plaisant à reprendre et j’espère vraiment bientôt lancer ça, ne fut-ce que de manière expérimentale pendant quelques jours.