Skip to content
09Étude de casSW · 09 sur 10

Trois runtimes, une équipe, un mois : un réseau d'affichage numérique pour le lieu, l'annonceur et l'écran.

Trois audiences, un seul produit. Les exploitants de lieux ne pouvaient pas gérer les plannings d'écrans, les annonceurs ne pouvaient pas acheter d'inventaire, et les écrans eux-mêmes avaient besoin d'un lecteur adapté à un matériel hétérogène. Nous avons bâti ces trois surfaces en 27 jours.

ClientConfidentiel
Année2026
Durée1 yrs
StackTypeScript · Next.js · React · PHP · Laravel · C++ · Qt · Supabase · AWS
Hero image for ooh-signage-networkFig 01 · Hero

Trois audiences, un seul produit, zéro hypothèse commune.

Un réseau de publicité OOH a trois utilisateurs distincts qui n'ont rien en commun. Les propriétaires de lieux veulent savoir ce qui passe sur leurs écrans, et quand. Les annonceurs veulent acheter des créneaux, cibler par localisation et voir le nombre d'impressions. Les écrans eux-mêmes ne sont pas du tout des utilisateurs — ce sont des appareils qui doivent recevoir les plannings de façon fiable et les jouer sans intervention humaine.

Le client avait un concept fonctionnel et une fenêtre de lancement serrée. Il lui fallait un backend de marketplace pour gérer les validations d'annonceurs, la facturation et la planification de campagnes. Une console pour que les exploitants et les propriétaires de lieux glissent des créneaux dans un calendrier. Et un runtime de lecteur capable de tourner sur des tablettes Android derrière la réception d'un hôtel, des bornes Linux dans un hub de transit et des set-top boxes Windows dans une chaîne de magasins — sans déployer une base de code différente sur chacun.

Le défaut commode pour le lecteur, c'était Electron. Electron embarque une instance de Chromium et un runtime Node, et il tourne partout. Il pèse aussi des centaines de mégaoctets, se met à jour de façon imprévisible et ajoute la complexité d'un navigateur à ce qui devrait être un lecteur média. Sur du matériel de borne contraint, l'empreinte comptait. L'histoire des mises à jour comptait davantage.

Un backend de marketplace, une console glisser-déposer et un lecteur natif qui tourne sur quatre systèmes d'exploitation.

Le backend est une application Laravel 12 multi-tenant qui gère tout ce dont la marketplace a besoin : intégration et validation des annonceurs, planification de campagnes avec découpage horaire et géociblage, réconciliation de facturation Stripe, téléversement et transcodage de médias mis en file via la couche de tâches d'arrière-plan de Laravel, et un modèle d'agrégation d'impressions qui alimente le reporting. Sanctum gère l'authentification de l'API. Les actifs vivent sur S3.

La console d'exploitation est une surface d'administration Next.js 16 avec un planificateur glisser-déposer bâti sur @dnd-kit. Les propriétaires de lieux voient leurs écrans et leurs créneaux. Les annonceurs voient leurs campagnes et leur portée. Le rôle administrateur voit tout. Les trois sont la même application, cloisonnée par rôle.

Le lecteur est en Qt 6 et C++20. Il tourne sur macOS, Linux, Windows et Android depuis une seule base de code CMake avec des presets par plateforme. Au démarrage, il se connecte à Supabase via WebSockets et s'abonne à son canal de planning. Quand l'exploitant déplace un créneau dans la console, le changement arrive à l'écran en temps réel — pas de polling, pas de cycle de rafraîchissement. Si le réseau tombe, le lecteur se replie sur son planning et ses médias mis en cache localement. L'écran continue de jouer.

F · 01Console multi-tenant
Propriétaires de lieux, annonceurs et administrateur sur une seule surface, cloisonnée par rôle. Pas de portails séparés, pas de bases de code dupliquées.
F · 02Planificateur glisser-déposer
Créneaux, géo-périmètres et tranches horaires agencés dans un calendrier bâti sur @dnd-kit. Les exploitants voient les conflits avant d'enregistrer.
F · 03Synchronisation du lecteur en temps réel
Les changements de planning sont poussés vers la flotte de lecteurs via les WebSockets Supabase dès que l'exploitant enregistre. Pas de polling, pas de redémarrage.
F · 04Lecteur capable de fonctionner hors-ligne
Qt Sql avec SQLite met en cache le planning et les médias en cours localement. Une coupure de Wi-Fi ne laisse pas l'écran vide.
F · 05Binaire natif multi-plateforme
Une seule base de code CMake, quatre plateformes cibles : macOS, Linux, Windows, Android. Chaque build est un binaire natif autonome — pas de Chromium, pas de V8.
F · 06Facturation de marketplace
Réconciliation de facturation Stripe et agrégation d'impressions dans le backend Laravel. Les annonceurs paient pour des impressions confirmées.

Le choix de Qt, et son coût face au défaut Electron.

Electron était le choix évident. Toute l'équipe connaissait JavaScript. La console d'administration était déjà en TypeScript. Un lecteur Electron aurait partagé un tiers de sa base de code avec la surface web. Nous l'avons écarté dès la première semaine.

La réalité du déploiement a été le facteur décisif. Le matériel cible allait des tablettes Android en 4G aux mini-PC Windows sur Wi-Fi d'entreprise, en passant par des bornes Linux aux images d'OS verrouillées. L'empreinte Chromium d'Electron — typiquement 150 à 300 Mo installés — était un refus net pour les appareils contraints. Le comportement de mise à jour automatique de Chromium, qui se heurte aux politiques d'OS de borne verrouillées, était un refus plus net encore pour les déploiements en entreprise.

Qt 6 avec C++20 donne un binaire natif par plateforme, compilé à environ 15 Mo sur Linux et moins de 30 Mo sur Windows et Android. Le chemin de mise à jour est un échange de fichier, orchestré par le lecteur lui-même. Il n'y a pas de moteur de navigateur, pas de V8, pas de broker de protocole Electron. La connexion WebSocket à Supabase passe par Qt Network ; la lecture média passe par Qt Multimedia. Le cache hors-ligne utilise Qt Sql avec SQLite. Chaque dépendance est livrée dans le binaire.

Un lecteur qui colle au matériel bat un lecteur qui colle à l'équipe.

Le coût, c'était le changement de contexte. L'équipe a fait tourner trois runtimes — PHP, TypeScript, C++ — en parallèle. Les tests d'intégration inter-runtimes ont exigé plus d'échafaudage qu'une stack mono-langage. La matrice de build CMake pour quatre plateformes cibles a ajouté de la complexité au CI. GitHub Actions lance quatre tâches de build du lecteur par push ; le build Linux basé sur Docker s'est avéré la base la plus fiable pour la reproductibilité.

Une plateforme OOH à trois niveaux, livrée en moins d'un mois.

La première version des trois surfaces était prête pour la production en 27 jours : 223 commits sur trois dépôts, CI multi-plateforme à chaque push, binaires du lecteur confirmés sur les quatre systèmes d'exploitation cibles avant la revue finale. Le backend de marketplace gère la réconciliation de facturation des annonceurs et l'agrégation d'impressions. La console gère la planification sans tableur. Le lecteur gère les coupures réseau sans laisser l'écran vide.

La plateforme est en production. La console d'exploitation est le système de référence des plannings d'écrans sur tout le réseau. La flotte de lecteurs reçoit les mises à jour en temps réel via les WebSockets Supabase — les changements de planning faits dans la console se propagent aux écrans physiques sans redémarrage ni synchronisation manuelle.

La morale du choix de stack est simple. Electron est la bonne réponse quand le confort de l'équipe et la vitesse de livraison sont les contraintes déterminantes. Ce n'était pas la bonne réponse ici. Choisissez le runtime de lecteur qui correspond à votre matériel, pas celui qui correspond à la zone de confort de votre équipe.

Citation / 04
On leur a dit que le lecteur devait tourner sur tout ce qu'on avait déjà acheté. Ils sont revenus avec un binaire qui le faisait.
FondateurRéseau de publicité extérieure
Outcome
Days to first production build
27
Repos shipped in parallel
3
Player target platforms
4
Real-time push (WebSocket)
< 1 s
NEXTÉtude de cas 10SW · 10 sur 10
Consumer wellness2018 — 2019

Quand la plateforme ne parle pas votre métier, vous écrivez les modules qui le font.