Три аудиторії, один продукт, нуль спільних припущень.
Мережа OOH-реклами має трьох різних користувачів, які не мають нічого спільного. Власники майданчиків хочуть знати, що йде на їхніх екранах і коли. Рекламодавці хочуть купувати слоти, таргетувати за локацією й бачити кількість показів. Самі екрани взагалі не користувачі — це пристрої, яким треба надійно отримувати розклади й програвати їх без втручання людини.
Клієнт мав робочу концепцію й стислий запусковий проміжок. Йому був потрібен бекенд-маркетплейс для обробки погоджень рекламодавців, білінгу й планування кампаній. Консоль для операторів і власників майданчиків, щоб перетягувати слоти в календар. І середовище виконання плеєра, що могло б працювати на Android-планшетах за стійкою рецепції готелю, Linux-кіосках на транзитному вузлі й Windows-приставках у роздрібній мережі — без розгортання окремої кодової бази на кожному.
Зручним дефолтом для плеєра був Electron. Electron пакує екземпляр Chromium і середовище виконання Node й працює всюди. Він також важить сотні мегабайтів, непередбачувано автооновлюється й додає складності цілого браузера до того, що має бути медіаплеєром. На обмеженому кіосковому обладнанні розмір мав значення. Питання оновлень мало значення ще більше.
Бекенд-маркетплейс, консоль з перетягуванням і нативний плеєр, що працює на чотирьох операційних системах.
Бекенд — це багатоорендний застосунок на Laravel 12, що обробляє все, що потрібно маркетплейсу: підключення й погодження рекламодавців, планування кампаній із розбивкою за частинами доби й геотаргетингом, звірку білінгу Stripe, завантаження й транскодування медіа в черзі через шар фонових завдань Laravel, а також модель агрегації показів, що живить звітність. Sanctum обробляє автентифікацію API. Ассети живуть на S3.
Операторська консоль — це адмін-поверхня на Next.js 16 із планувальником з перетягуванням, побудованим на @dnd-kit. Власники майданчиків бачать свої екрани й свої слоти. Рекламодавці бачать свої кампанії й своє охоплення. Роль адміністратора бачить усе. Усі три — це той самий застосунок, розмежований за роллю.
Плеєр — це Qt 6 і C++20. Він працює на macOS, Linux, Windows та Android з єдиної CMake-кодової бази з пресетами для кожної платформи. На старті він під'єднується до Supabase через WebSockets і підписується на свій канал розкладу. Коли оператор переміщує слот у консолі, зміна доходить до екрана в реальному часі — без опитування, без циклу оновлення. Якщо мережа зникає, плеєр відкочується до локально кешованих розкладу й медіа. Екран продовжує програвати.
- F · 01Багатоорендна консоль
- Власники майданчиків, рекламодавці й адміністратор в одній поверхні, розмежовані за роллю. Без окремих порталів, без дубльованих кодових баз.
- F · 02Планувальник з перетягуванням
- Слоти, геозони й частини доби розставлені в календарі, побудованому на @dnd-kit. Оператори бачать конфлікти до збереження.
- F · 03Синхронізація плеєра в реальному часі
- Зміни розкладу надсилаються в парк плеєрів через Supabase WebSockets у мить, коли оператор зберігає. Без опитування, без перезапуску.
- F · 04Плеєр зі здатністю працювати офлайн
- Qt Sql із SQLite кешує поточний розклад і медіа локально. Зникнення Wi-Fi не затемнює екран.
- F · 05Кросплатформний нативний бінарник
- Одна CMake-кодова база, чотири цільові платформи: macOS, Linux, Windows, Android. Кожна збірка — самодостатній нативний бінарник — без Chromium, без V8.
- F · 06Білінг маркетплейсу
- Звірка білінгу Stripe і агрегація показів у бекенді на Laravel. Рекламодавці платять за підтверджені покази.
Вибір Qt і чого він коштував порівняно з дефолтом Electron.
Electron був очевидним вибором. Уся команда знала JavaScript. Адмін-консоль уже була на TypeScript. Electron-плеєр поділяв би третину своєї кодової бази з вебповерхнею. Ми відкинули його в перший тиждень.
Реальність розгортання була вирішальним чинником. Цільове обладнання варіювалося від Android-планшетів на 4G до Windows-міні-ПК на корпоративному Wi-Fi і Linux-кіосків із заблокованими образами ОС. Розмір Chromium в Electron — зазвичай 150–300 МБ у встановленому вигляді — був категоричним «ні» для обмежених пристроїв. Поведінка автооновлення Chromium, що конфліктує із заблокованими політиками кіоскової ОС, була ще категоричнішим «ні» для корпоративних розгортань.
Qt 6 з C++20 дає нативний бінарник для кожної платформи, скомпільований приблизно до 15 МБ на Linux і менш ніж 30 МБ на Windows та Android. Шлях оновлення — це заміна файлу, оркестрована самим плеєром. Немає ні браузерного рушія, ні V8, ні Electron-брокера протоколу. З'єднання WebSocket із Supabase йде через Qt Network; відтворення медіа йде через Qt Multimedia. Офлайн-кеш використовує Qt Sql із SQLite. Кожна залежність постачається в бінарнику.
Плеєр, що пасує обладнанню, перемагає плеєр, що пасує команді.
Ціною було перемикання контексту. Команда вела три середовища виконання — PHP, TypeScript, C++ — паралельно. Інтеграційні тести між середовищами вимагали більше каркасу, ніж стек однією мовою. CMake-матриця збірки для чотирьох цільових платформ додала складності CI. GitHub Actions запускає чотири завдання збірки плеєра на кожен push; збірка Linux на основі Docker виявилася найнадійнішою базою для відтворюваності.
Трирівнева OOH-платформа, випущена менш ніж за місяць.
Перша версія всіх трьох поверхонь була готова до продакшну за 27 днів: 223 коміти у трьох репозиторіях, багатоплатформний CI на кожен push, бінарники плеєра підтверджені на всіх чотирьох цільових операційних системах до фінального ревʼю. Бекенд-маркетплейс обробляє звірку білінгу рекламодавців і агрегацію показів. Консоль обробляє планування без таблиці. Плеєр обробляє зникнення мережі без затемнення екрана.
Платформа працює. Операторська консоль — це система обліку для розкладів екранів по всій мережі. Парк плеєрів отримує оновлення в реальному часі через Supabase WebSockets — зміни розкладу, зроблені в консолі, поширюються на фізичні екрани без перезапуску чи ручної синхронізації.
Мораль вибору стеку проста. Electron — правильна відповідь, коли зв'язувальними обмеженнями є комфорт команди й швидкість постачання. Тут він не був правильною відповіддю. Обирайте середовище виконання плеєра, що відповідає вашому обладнанню, а не те, що відповідає зоні комфорту вашої команди.
Ми сказали їм, що плеєр має працювати на всьому, що ми вже купили. Вони повернулися з бінарником, який працював.
