У нашому офісі в Антверпені встановлено три колонки, які працюють майже безперервно. Зранку хтось запускає сесію у Spotify або Apple Music, після обіду її «перехоплює» інший, а ближче до вечора ми вже спантеличено переглядаємося, намагаючись зрозуміти: те, що лунає — це ще пісня чи вже сигнал тривоги? Смаки в команді, скажімо так, еклектичні, тому питання «Що це в біса грає?» виникає досить часто.
На останньому хакатоні ми (Марцелі, Нік та Тім) вирішили перетворити цей фоновий хаос на проєкт. Так з’явився Live at Spatie.
Наші цілі
Ми не прагнули переосмислити Spotify, а лише хотіли розв'язати дві проблеми, які нас дратували:
- Спільна черга без перехоплення контролю. Кожен має додавати треки у загальний список. Більше жодних «можна я швидко закину це через AirPlay?», після яких наступає три хвилини тиші, поки чийсь телефон намагається перепідключитися.
- Миттєве розуміння контенту. З огляду на наш репертуар, відповідь на питання «Що це за трек?» має бути помітною з одного погляду, а не обговорюватися у Slack.
Усе, що ми зробили далі, випливає з цих двох завдань.
Що ми створили
Live at Spatie — це невелика Laravel + React обгортка над Owntone. Owntone — це open-source медіасервер, який ми обрали через простоту та вбудовану підтримку наших колонок HomePod. Завдяки Live at Spatie кожен у команді може шукати пісні, додавати їх у чергу, бачити поточний трек і скіпати те, що «ріже вуха». Ми додали спрощений плеєр у шапці сайту, лідерборд активних контриб’юторів збоку та Slack-бота, який дозволяє керувати музикою, не відкриваючи застосунок.

Інтеграція Owntone у Laravel
Owntone має цілком адекватний REST API, тому основна частина інтеграції — це один клас App\Services\OwnTone, побудований на Laravel Http facade. Більшість методів — це прості обгортки: nowPlaying(), queue(), toggle(), next(), setVolume(). Кожен окремо не є чимось особливим, але разом вони роблять роботу із застосунком дуже зручною.

Додавання треків (та трекінг винуватців)
Пошук працює через Spotify API. Коли хтось додає трек, ми передаємо Spotify URI до Owntone, порівнюємо стан черги «до» та «після», щоб зрозуміти, який ID у черзі Owntone відповідає новому запису. Цей зв'язок зберігається у pivot-таблиці QueueItemUser — так ми завжди знаємо, хто саме додав пісню.
Кожна пісня, яку прослухали більш ніж на 50%, логується у таблицю PlayHistory. Це дозволяє формувати лідерборд на головній сторінці: топові діджеї отримують визнання, а пасивні слухачі — жартівливі зауваження за відсутність ініціативи.

Slack-бот з MCP-інтелектом
Це була улюблена частина хакатону. Застосунок відкриває MCP-сервер за адресою /mcp/music за допомогою laravel/mcp. Реєстрація інструментів виглядає максимально чисто:
#[Name('LiveAtSpatie')]
class MusicServer extends Server
{
protected array $tools = [
ListUsersTool::class,
SearchSongsTool::class,
AddToQueueTool::class,
NowPlayingTool::class,
PlayerControlTool::class,
SetVolumeTool::class,
// …
];
}
Загалом ми реалізували одинадцять інструментів: пошук, черга, пропуск треків, гучність, історія тощо. На боці Slack ми підключили цей MCP до екземпляра OpenClaw. Тепер можна просто написати боту: «Додай у чергу весь альбом In Rainbows» — і він це зробить. Бот також підтягує обкладинку альбому та цікаві факти про виконавця, що остаточно вирішує проблему «що це грає?».

Де це працює
Застосунок Laravel розгорнуто через Laravel Forge. Owntone працює на Raspberry Pi, підключеному до офісного Wi-Fi (це необхідно для зв'язку з колонками). Ми об’єднали їх через Tailscale: Raspberry Pi приєднується до нашої мережі tailnet, і застосунок на Forge бачить його як пристрій у локальній мережі. Жодного прокидання портів чи публічного доступу до Owntone.
Також ми вивели окремий віджет Live at Spatie на наш офісний дашборд (великий телевізор у центрі приміщення), створений на базі laravel-dashboard. Тепер назву поточного треку можна дізнатися, просто піднявши голову.

Розробка цього проєкту принесла купу задоволення і точно покращила музичну атмосферу в нашому офісі.
Подивитися, що ми слухаємо прямо зараз, можна тут: https://liveat.spatie.be