Speaking turns in your videocalls


I’ve been co-hosting a speaking group about polyamory in Paris for the past few years, in which we used to organize the debate by writing down speech requests on a piece of paper, and distributing them in order. The groups were between 60 and 100 people in average.

However, you have probably noticed that there’s a tiny pandemic going on. Those meetups had thus to stop without notice, leaving a big void in our social activities. Quickly enough, some of the co-hosts migrated online, on a videoconferencing system that I will not name but which starts with a Z… I don’t know if you have ever attended a videocall with 50 people, but with the latency and the background noises it’s impossible to just have an informal chat. So we tried to recreate the structure we had in our goups, since it worked well for us: speech requests, written down on a list, distributed in order.

We soon had to face a few issues: asking for a turn in this piece of software is not as easy at it sounds. At first we had to watch everybody’s thumbnails to check if somebody was raising their hand, but on large videocalls there can be several pages of thumbnails. Then we told attendees that they could ask for a turn in the text chat, and it just added one more place to look at for us 😉 Then the feature to « raise hand » by clicking a button was added, which works well when you can find it, so not everybody uses it. Once the request has been noticed, we write it down on a Google doc shared between hosts, then we announce the turn and remove the line from the doc. And on top of that, we checked our speaking time counter to avoid monologues.
It works, but it’s quite a bit of work, it requires many pairs of eyes looking at screens and the text chat, etc.

Making life simpler

I thought there was an opportunity to simplify all that with software. The goal would be to allow participants to request a speaking turn, that would automatically be added to a list that hosts could see and reorder. And it would be removed when the turn is over. Oh, and also manage the issue of speech duration, so that we don’t have to handle the counter on the side. And since our counter produces statistics relative to gender, we want to keep that feature in the new app.

Alright, we know what we need to do, we just have to do it. I started writing at the end of year 2020, and after a failed start I’m happy to say that we’ve been using it for 3 meetings, and that it works rather well 🙂

The result

The app is at https://speakinglist.net. You can go create an event, and to simulate participants you can for example open a private browser window or test with other co-hosts. There’s also a how-to page that you can look at.

The admin interface that you’ll end up on when creating an event has been designed to be displayed on a computer, while the participant interface has been designed to be used on a phone. This way the participants can have the videocall on their computer, and in hand a button to request speaking turns. It is also possible to display the participant interface on the computer of course, it’ll work identically.


The fact that participants declare their social categories (gender and race for now, optional and selectable by organizers) allows for the generation of interesting statistics on the distribution of speech between sides of the power structure. It also lets organizers reorder the waiting list to make sure it’s not always the same group of people who speak (hello white males, we see you). By objectively measuring the speaking time, we can realize there’s an issue, which is the first step to solving it. For example, in our groups we have found that even though there are less men present, they usually speak more often and longer than women and non-binary folks in proportion to their number. I have also noticed that when we tell them at the beginning of the event that we measure it and that we’d like them to make an effort, there is less difference. As we say in engineering, « you can’t optimize what you can’t measure ».

It’s Geeky time

For me, writing a program for a non-profit organization is always an opportunity to learn new tech and software libraries that I don’t know yet. Here, the main challenge is real-time. When we give a turn to someone, it has to be instantly reflected in their interface. For that we prefer a push method to a poll method, heavier in resources but more importantly much slower.

In the world of the web, this means websocket, that I never got to play with yet. Yay! 🙂 I took this opportunity to learn a backend framework that natively handles asynchronous operations. I started with FastAPI, that I’ve been wanting to test for a while, and we’ll see later why I didn’t keep it.

I don’t have so many opportunities to test new tech, so I try to cram as many interesting things as I can in them. I’ve also tried GraphQL as the API protocol and I must say I’m very happy with it. On top of it, GraphQL already contains a push mechanism, called subscriptions, which is a very good fit for me. However, FastAPI uses a version of Starlette that does not handle GraphQL’s subscriptions, so I dropped FastAPI to use Starlette directly.

Async is beautiful when it works, but not everything is ready for native async just yet in the Python world. I wanted to use the SQLAlchemy ORM, because the database library that Starlette offers does not do ORM and I do like it. Unfortunately, SQLAlchemy does not handle async yet (it should arrive in the next version, 1.4). So I used it in normal blocking mode, thinking that database connections are pretty fast since they are on the same server. It should all work fiiiiine.

It didn’t. Well, it did until there were more than 5 simultaneous connections, at which point we exhausted SQLAlchemy’s connection pool, which thus blocked the request until connections were freed. And this, in an async mode, never happens: when it’s blocked it’s blocked 🙂 To sum it up, on the first real-world use, the program fell over like a big sea lion drunk on bavarian beer. Should I have stress tested the app? Absolutely.

I more or less worked around it by raising the connection pool to 50 and crossing my fingers hoping there there is never more than 50 simultaneous database requests. I stress-tested it, it works fine with several hundreds of simultaneous users. If we ever do meetups that big, I think we’ll have other problems first. But yeah, I’d be happier when SQLAlchemy handles async natively. Until 1.4 is released, it’ll have to do.

On the frontend side, it’s only classics. React with Customize-CRA, Material-UI and Apollo for GraphQL, all written in Typescript. I must say that I am very pleasantly surprised with Apollo, it allowed me to entirely avoid Redux!

The app is obviously under a Free Software license, the AGPL. The documentation isn’t very detailed but it’s basically composed of an SQL server, the Python web app server Uvicorn (for async), Nginx in front of it all, and Redis in the backend to handle message queuing. The source code is here, and I provide a few configuration examples for deployment.

To conclude

It now works rather well. Everybody does not use the app during events, but we can manually add reluctant participants and handle their requests like the others on the waiting list. The group seems happy about it, I’m happy about it, I think it’s mature enough to be used by others.

Feel free to tell me what you think of it, here in the comments or by email. If you want to help, I think that the app could use a graphic designer’s skills for a new logo, colors, fonts, etc. It’s currently translated into French and English, but I’m sure the English translation could really be improved. You can also add other languages if you feel like it. I accept contributions in the form of code too, of course, if you can make sense of mine 😉

I am looking for feedback, please tell me if you use it, with which group sizes, and what you think of it. I wish you good videocalls!

Des tours de parole dans vos visioconfs

Le contexte

Je co-anime un groupe de parole à Paris autour de la polyamorie depuis quelques années, groupe dans lequel on avait l’habitude d’organiser le débat en notant les demandes de prise de parole sur un bout de papier, et en distribuant la parole dans l’ordre au fur et à mesure. Les groupes allaient de 60 à 100 personnes environ.

Or, il ne vous a pas échappé qu’il y a en ce moment une petite pandémie. Ces réunions-là se sont donc arrêtées du jour au lendemain, laissant un gros vide dans nos activités associatives et sociales. Assez rapidement, certain⋅es orgas ont migré en ligne, sur un logiciel de visioconférence que je ne citerai pas mais qui commence par Z… Je ne sais pas si vous avez déjà participé à une visioconférence à 50 personnes, mais avec la latence et les bruits de fond, c’est absolument impossible de mener une discussion informelle. On a donc essayé de recréer la structure qu’on avait dans nos groupes et qui marchait bien : demande de prise de parole, inscription sur une liste, distribution des tours.

Mais voilà, demander la parole sur ce logiciel c’est pas aussi évident. Au début il fallait regarder toutes les vignettes des gens pour regarder qui lève la main, mais il y a plusieurs pages de vignettes quand on est nombreux/ses. Puis on a dit qu’on pouvait demander sur le chat textuel, et ça nous a juste ajouté un nouvel endroit à regarder 😉 Ensuite le logiciel a ajouté la possibilité de « lever la main » en appuyant sur un bouton, ce qui marche bien mais faut le trouver, le bouton, donc tout le monde ne l’utilise pas. Une fois que la demande est repérée, on l’inscrit sur un document Google partagé entre orgas, puis on distribue la parole en enlevant la ligne du document. Enfin, on gardait un œil sur notre compteur de temps de parole, pour éviter les monologues.
Ça marche, mais c’est fastidieux, ça demande plein de paires d’yeux qui scrutent les écrans et le chat textuel, etc.

Se simplifier la vie

Je me suis dit que c’était l’occasion de simplifier ça avec un programme. Le but serait de permettre aux participant⋅es de demander un tour de parole, qui s’inscrirait automatiquement dans notre liste côté orga. Et que le passage de parole supprime automatiquement le nom de la liste. Oh, et aussi que ça gère en même temps la question du temps de parole, pour qu’on ait pas le compteur à gérer à côté. Et comme notre compteur trie et produit des stats en fonction du genre, il faudrait garder cette possibilité dans la nouvelle appli.

Bon, on a le cahier des charges, y’a plus qu’à. J’ai commencé à fin 2020, et après un démarrage un peu raté je suis assez content de dire qu’on en est à notre 3e utilisation et que ça marche plutôt bien 🙂

Le résultat

Le site est à l’adresse : https://speakinglist.net. Vous pouvez aller y créer votre évènement, et pour simuler des participant⋅es vous pouvez par exemple ouvrir une fenêtre de navigation privée dans votre navigateur, ou tester avec d’autres orgas.

L’interface d’administration sur laquelle vous arrivez en créant un évènement est prévue pour être affichée sur un ordinateur, alors que l’interface pour participant⋅e est prévue pour être utilisée sur un téléphone. Comme ça les participant⋅es ont a la visioconf sur l’ordi, et dans la main le bouton pour demander un tour de parole. On peut aussi, bien sûr, préférer afficher aussi son interface de participant⋅e sur l’ordi, et ça marchera de la même façon.

Lestat ? Lestat le Vampire ?

Non, les stats ! (désolé)
Le fait que les participant⋅es déclarent leurs catégories sociales (genre et race pour l’instant, au choix des orgas) permet de faire des stats intéressantes sur la répartition de la parole entre dominants et dominé⋅es. Et aussi de réorganiser la liste de parole pour que ce ne soient pas toujours les mêmes qui parlent (coucou les mecs blancs on vous voit). En mesurant objectivement le temps de parole, on peut prendre conscience du problème, ce qui est la première étape pour le résoudre. Par exemple, dans nos débats on se rend compte que même si les mecs sont moins nombreux, ils parlent la plupart du temps plus souvent et plus longtemps que les femmes et les personnes non-binaires en proportion de leur nombre. J’ai aussi remarqué que quand on leur dit, en début d’évènement, qu’on mesure cela et qu’on aimerait qu’ils fassent un effort là-dessus, la différence est moindre. Comme on dit en ingénierie, « on ne peut optimiser que ce que l’on peut mesurer ».

Geekerie !

Pour moi, faire un programme dans un cadre associatif est toujours l’occasion d’essayer des technos et des bibliothèques logicielles que je ne connais pas encore. Ici, le défi principal est le temps-réel. Quand on donne la parole à quelqu’un, ça doit se refléter instantanément sur son interface. Pour cela on va préférer le push au polling, plus gourmand en ressources et surtout beaucoup plus lent.

Ça, dans le monde du web, ça veut dire websocket, que je n’avais encore jamais utilisé. Yay ! 🙂 J’en ai profité pour sélectionner pour le backend un framework qui gère nativement l’asynchrone. J’ai commencé avec FastAPI, que je voulais tester depuis longtemps, et on va voir plus tard pourquoi je ne l’ai pas gardé.

Des occasions de tester de nouvelles technos, j’en ai pas 10 par an, donc j’essaye de caser le maximum de trucs intéressants dedans. J’en ai profité pour essayer GraphQL comme protocole d’API, et je dois dire que je suis très content. En plus, GraphQL contient déjà un mécanisme pour le push, appelé subscriptions, ce qui est très adapté à mon cas. Par contre, FastAPI utilise une version de Starlette qui ne gère pas bien les subscriptions dans GraphQL, j’ai donc abandonné FastAPI pour utiliser directement Starlette.

L’asynchrone, c’est beau quand ça marche, mais tout n’est pas encore prévu pour fonctionner nativement avec. Je voulais notamment utiliser l’ORM de SQLAlchemy, parce que la bibliothèque de base de données proposée avec Starlette ne fait pas d’ORM et que j’aime bien ça. Malheureusement, SQLAlchemy ne fait pour l’instant pas d’asynchrone (ça devrait arriver dans la prochaine version, la 1.4). Donc je l’ai utilisée en mode normal (bloquant) en me disant que les connexions à la base de données sont rapides, puisqu’elles sont sur le même serveur. Alleeeeeez, ça va passeeeeer.

C’est pas passé. Enfin, ça a marché, mais dès qu’il y a eu plus de 5 connexions simultanées, on a épuisé le pool de connexions de SQLAlchemy, qui a donc bloqué la demande de nouvelles connexions en attendant qu’une se libère. Et ça, en asynchrone, ça n’arrive jamais : quand c’est bloqué c’est bloqué 🙂 Donc en gros, à la première utilisation en conditions réelles, le programme s’est vautré comme une grosse otarie bourrée à la bière bavaroise. Est-ce que j’aurais dû faire un stress test de l’appli ? Absolument.

J’ai plus ou moins contourné ça en augmentant la taille du pool à 50 connexions et en croisant les doigts pour qu’on ait jamais 50 demandes à la base de données exactement en même temps. J’ai stress-testé, on passe tranquille avec plusieurs centaines d’utilisateurs simultanés. Si un jour on fait des visioconfs débat avec plusieurs centaines de participant⋅es, je crois qu’on aura d’autres problèmes avant celui-là. Mais bon, je serai quand même plus rassuré quand SQLAlchemy gèrera l’asynchrone nativement. En attendant la version finale de la 1.4, ça va tenir comme ça.

Côté front-end, du classique, React avec Customize-CRA, Material-UI et Apollo pour le GraphQL, le tout en Typescript. Je dois dire que je suis très agréablement surpris par Apollo, ça m’a permis d’éviter Redux complètement !

Il va sans dire que l’appli est sous licence libre AGPL. La documentation d’installation n’est pas hyper détaillée mais c’est en gros à base de base de données SQL, serveur d’application Python Uvicorn (pour l’asynchrone), Nginx devant tout ça, et Redis en backend pour les files d’attente de messages. Le code source est ici, et je fournis quelques exemples de fichiers de conf pour le déploiement.


Là, ça marche plutôt bien. Tout le monde n’utilise pas l’appli dans les évènements, mais on peut ajouter manuellement les participant⋅es réfractaires et les traiter comme les autres dans la liste d’attente. Le groupe est content, je suis content, je pense que c’est suffisamment mûr pour être utilisé par d’autres.

N’hésitez pas à me dire ce que vous en pensez, ici en commentaire ou par mail ! Si vous voulez aider, je pense que l’appli pourrait gagner à ce qu’un⋅e graphiste se penche dessus, fasse un vrai logo, choisisse des couleurs, des typos sympa, etc. Ça fonctionne actuellement en français et en anglais, mais si vous voulez ajouter votre langue c’est aussi tout à fait possible. J’accepte aussi les contributions sous forme de code bien sûr, si vous vous y retrouvez dans le mien 😉

Je suis très preneur de retours, dites-moi si vous l’utilisez, sur quelles tailles de groupes, et ce que vous en pensez. Bonnes visios !

Nouveau blog

C’est pas une surprise, le blogging c’est pas mon fort. Je me déclare donc aujourd’hui en banqueroute de blog, j’annule toutes mes dettes et je recommence à zéro.

Qui sait, peut-être que cette fois-ci ça se passera mieux, et que c’était seulement la pression de devoir blogguer souvent qui me retenait ? Vous savez, comme quand vous vous dites : « Rha la la je devrais écrire à ce vieux pote que j’ai pas vu depuis longtemps. » Mais après tout ce temps, ça va être un message important, il faut faudra une vraiment bonne raison, une vraiment grande annonce, pour le justifier.

Fuck it. J’ai pas de bonne raison. J’ai pas de grande annonce. Mais je suis là, et je vais essayer d’y rester.