Tre pubblici, un prodotto, zero assunzioni condivise.
Una rete pubblicitaria OOH ha tre utenti distinti che non hanno nulla in comune. I proprietari delle sedi vogliono sapere cosa va in onda sui loro schermi e quando. Gli inserzionisti vogliono acquistare slot, fare targeting per posizione e vedere i conteggi delle impression. Gli schermi stessi non sono affatto utenti — sono dispositivi che devono ricevere i palinsesti in modo affidabile e riprodurli senza intervento umano.
Il cliente aveva un concept funzionante e una finestra di lancio stretta. Avevano bisogno di un backend di marketplace per gestire le approvazioni degli inserzionisti, la fatturazione e la pianificazione delle campagne. Di una console per gli operatori e i proprietari delle sedi per trascinare gli slot in un calendario. E di un runtime player capace di girare su tablet Android dietro la reception di un hotel, kiosk Linux in un hub di trasporti e set-top box Windows in una catena retail — senza distribuire un codice diverso a ciascuno.
Il default comodo per il player era Electron. Electron impacchetta un'istanza di Chromium e un runtime Node, e gira ovunque. Pesa anche centinaia di megabyte, si auto-aggiorna in modo imprevedibile e aggiunge la complessità di un browser intero a ciò che dovrebbe essere un lettore multimediale. Su hardware kiosk vincolato, l'ingombro contava. La gestione degli aggiornamenti contava di più.
Un backend di marketplace, una console drag-and-drop e un player nativo che gira su quattro sistemi operativi.
Il backend è un'applicazione Laravel 12 multi-tenant che gestisce tutto ciò di cui il marketplace ha bisogno: onboarding e approvazioni degli inserzionisti, pianificazione delle campagne con day-parting e geo-targeting, riconciliazione della fatturazione Stripe, upload e transcodifica dei media accodati tramite il livello di job in background di Laravel e un modello di aggregazione delle impression che alimenta la reportistica. Sanctum gestisce l'autenticazione delle API. Gli asset risiedono su S3.
La console operativa è una superficie di amministrazione Next.js 16 con uno scheduler drag-and-drop costruito su @dnd-kit. I proprietari delle sedi vedono i loro schermi e i loro slot. Gli inserzionisti vedono le loro campagne e la loro copertura. Il ruolo admin vede tutto. Tutti e tre sono la stessa applicazione, regolata per ruolo.
Il player è in Qt 6 e C++20. Gira su macOS, Linux, Windows e Android da un unico codice CMake con preset per piattaforma. All'avvio si connette a Supabase via WebSocket e si sottoscrive al canale del proprio palinsesto. Quando l'operatore sposta uno slot nella console, la modifica arriva allo schermo in tempo reale — niente polling, nessun ciclo di refresh. Se la rete cade, il player ripiega sul palinsesto e sui media in cache locale. Lo schermo continua a riprodurre.
- F · 01Console multi-tenant
- Proprietari di sedi, inserzionisti e admin in un'unica superficie, regolati per ruolo. Nessun portale separato, nessun codice duplicato.
- F · 02Scheduler drag-and-drop
- Slot, geo-recinzioni e fasce orarie disposti in un calendario costruito su @dnd-kit. Gli operatori vedono i conflitti prima di salvare.
- F · 03Sincronizzazione del player in tempo reale
- Le modifiche al palinsesto vengono inviate alla flotta di player tramite WebSocket Supabase nel momento in cui l'operatore salva. Niente polling, nessun riavvio.
- F · 04Player con funzionamento offline
- Qt Sql con SQLite memorizza in cache locale il palinsesto e i media correnti. Una caduta del Wi-Fi non oscura lo schermo.
- F · 05Binario nativo multipiattaforma
- Un solo codice CMake, quattro piattaforme di destinazione: macOS, Linux, Windows, Android. Ogni build è un binario nativo autonomo — niente Chromium, niente V8.
- F · 06Fatturazione del marketplace
- Riconciliazione della fatturazione Stripe e aggregazione delle impression nel backend Laravel. Gli inserzionisti pagano per le impression confermate.
La scelta di Qt, e cosa è costata rispetto al default Electron.
Electron era la scelta ovvia. L'intero team conosceva JavaScript. La console di amministrazione era già in TypeScript. Un player Electron avrebbe condiviso un terzo del suo codice con la superficie web. L'abbiamo escluso nella prima settimana.
La realtà del deployment è stata il fattore decisivo. L'hardware di destinazione spaziava da tablet Android su 4G a mini-PC Windows su Wi-Fi aziendale a kiosk Linux con immagini di sistema operativo bloccate. L'ingombro di Chromium di Electron — tipicamente 150–300 MB installato — era un no categorico per i dispositivi vincolati. Il comportamento di auto-aggiornamento di Chromium, che entra in conflitto con le policy bloccate del sistema operativo dei kiosk, era un no ancora più categorico per i deployment aziendali.
Qt 6 con C++20 dà un binario nativo per piattaforma, compilato a circa 15 MB su Linux e sotto i 30 MB su Windows e Android. Il percorso di aggiornamento è una sostituzione di file, orchestrata dal player stesso. Non c'è motore di browser, niente V8, nessun broker di protocollo Electron. La connessione WebSocket a Supabase passa per Qt Network; la riproduzione multimediale passa per Qt Multimedia. La cache offline usa Qt Sql con SQLite. Ogni dipendenza viaggia nel binario.
Un player che si adatta all'hardware batte un player che si adatta al team.
Il costo è stato il context-switching. Il team ha gestito tre runtime — PHP, TypeScript, C++ — in parallelo. I test di integrazione cross-runtime hanno richiesto più impalcatura di quanto avrebbe fatto uno stack a linguaggio unico. La matrice di build CMake per quattro piattaforme di destinazione ha aggiunto complessità alla CI. GitHub Actions esegue quattro job di build del player a ogni push; la build Linux basata su Docker si è dimostrata la baseline più affidabile per la riproducibilità.
Una piattaforma OOH a tre livelli, pubblicata in meno di un mese.
La prima versione di tutte e tre le superfici era pronta per la produzione in 27 giorni: 223 commit su tre repository, CI multipiattaforma a ogni push, binari del player confermati su tutti e quattro i sistemi operativi di destinazione prima della revisione finale. Il backend di marketplace gestisce la riconciliazione della fatturazione degli inserzionisti e l'aggregazione delle impression. La console gestisce la pianificazione senza un foglio di calcolo. Il player gestisce le cadute di rete senza oscurare lo schermo.
La piattaforma è live. La console operativa è il sistema di riferimento per i palinsesti degli schermi su tutta la rete. La flotta di player riceve aggiornamenti in tempo reale tramite WebSocket Supabase — le modifiche al palinsesto effettuate nella console si propagano agli schermi fisici senza un riavvio o una sincronizzazione manuale.
La morale della scelta di stack è semplice. Electron è la risposta giusta quando la comodità del team e la velocità di consegna sono i vincoli stringenti. Non era la risposta giusta qui. Scegli il runtime del player che si adatta al tuo hardware, non quello che si adatta alla zona di comfort del tuo team.
Abbiamo detto loro che il player doveva girare su tutto ciò che avevamo già comprato. Ci sono tornati con un binario che lo faceva.
