Tutorial 3: una scaletta con il pulsante GO in una UI web
Fai girare uno show con tre brani, lo vedi in una pagina web incorporata in una finestra e fai partire il brano successivo con un pulsante GO scritto in HTML e JavaScript. La pagina non tocca mai l’engine direttamente: invia comandi e legge topic attraverso il bridge della UI.
Tempo: circa 30 minuti. Ti servono JUCE 9.0.3 e il satellite
shuffle_juce
(tutorial 2), il cui JuceWebUiHost incorpora la pagina, e la webview della
piattaforma: runtime e SDK di WebView2 su Windows, WKWebView su macOS,
WebKitGTK su Linux. Nessun hardware audio: i brani suonano su un engine
simulato.
flowchart LR
page["page<br/>index.html, app.js"] -- "call transport.start_next_song" --> bridge["UI bridge"]
bridge --> session["command session"] --> show["setlist::Show"]
show -- "setlist.songs changed" --> bridge -- "snapshot, then deltas" --> page
1. Esegui il programma finito
Sezione intitolata “1. Esegui il programma finito”Il programma è examples/juce/webui di shuffle_juce, compilato ed eseguito dalla sua CI. Configura shuffle_juce con JUCE (tutorial 1, passo 4), poi, nel checkout di shuffle_juce:
cmake --build --preset windows-msvc --target example_juce_webuiEseguilo dall’albero di build di shuffle_juce (cmake-build/<preset>/examples/example_juce_webui_artefacts/...):
| Come lo lanci | Cosa succede |
|---|---|
| senza argomenti | una finestra con la scaletta, GO, Next e Stop |
--smoke |
la finestra si apre, il programma preme GO nella pagina ed esce quando il brano suona |
--headless |
nessuna finestra: una pagina scriptata pilota lo stesso bridge (è quello che esegue la CI; funziona senza webview) |
Premi GO: parte “Tidewater” e la lista lo segna come in riproduzione.
Anche Next e Stop sono comandi. Quando la sessione ha bisogno che una
persona confermi una richiesta durante uno show in corso, la pagina mostra la
conferma in attesa (dal topic session) in una finestra di dialogo.
2. Lo stato che la pagina vede: un topic
Sezione intitolata “2. Lo stato che la pagina vede: un topic”Un dominio mostra il suo stato alle pagine attraverso una TopicSource: un
descrittore costante con uno JSON Schema e una read() che restituisce il
valore attuale. SetlistTopics dell’esempio restituisce i brani come
collezione con chiavi (un oggetto indicizzato per identificatore, più un
array order), così una modifica costa un delta piccolo:
constexpr ui_bridge::TopicDescriptor kSetlistTopics[] { { .name = "setlist.songs", .summary = "The songs of the set list in order, the current one and whether it plays.", .schema = kSongsSchema, .maxRate = 30.0 },};Chiama listener_->topicChanged ("setlist.songs") quando lo show segnala un
evento; il bridge ripubblica anche dopo ogni comando applicato.
3. La composition root
Sezione intitolata “3. La composition root”Tutto tranne la vista, nell’ordine in cui viene costruito (la struct Engine
dell’esempio): i fake dal lato engine, lo setlist::Show, il catalogo e la
sessione dei comandi, i topic, il tema, il bridge.
commands::Catalogue catalogue = std::move (*commands::buildCatalogue ({ setlist::setlistCommands(), commands::sessionCommands() }).value);commands::CommandSession session { catalogue, show, clock };
SetlistTopics topics { show };ui_bridge::ThemeSource theme { stageDark() }; // the ui.theme topic: CSS custom properties
ui_bridge::TopicSource* sources[] { &topics, &theme };registry = std::move (*ui_bridge::buildTopicRegistry (sources).value);bridge = std::make_unique<ui_bridge::Bridge> (session, registry, clock, ui_bridge::BridgeSettings { .serverName = "examples/juce/webui", .serverVersion = "1.0.0" });Poi la vista e il collegamento con la pagina:
juce_adapter::JuceTaskQueue tasks;juce_adapter::JuceWebUiHost view; // a juce::Componentui_bridge::WebUiPage page (*app.bridge, view, tasks); // one bridge connection per page loadpage.load (app.content()); // the packaged files plus the generated clientUn timer di controllo chiama bridge->tick() più o meno una volta per
fotogramma del display.
4. La pagina
Sezione intitolata “4. La pagina”page/index.html contiene una lista
e tre pulsanti; page/app.js
si connette e li collega. Due file non si scrivono a mano:
shuffle_bridge.js è il runtime fisso (core/ui_bridge/web/), e
show_api.js viene generato dal catalogo dell’applicazione stessa
all’avvio:
import { BridgeClient, LiveRegions, accessibleName, bindTheme, inProcessTransport } from "./shuffle_bridge.js";import { catalogueEntries, catalogueHash, commands, createApi, subscribeTopic } from "./show_api.js";
const client = new BridgeClient (inProcessTransport (), { name: "Set list page", version: "1.0", capabilities: ["plans", "confirm", "events", "theme"], catalogueHash, catalogueEntries,});const api = createApi (client);
$("go").addEventListener ("click", () => run (() => api.transport.start_next_song ({})));
await client.connect ();bindTheme (client);subscribeTopic (client, "setlist.songs", {}, renderSongs);api.transport.start_next_song è il nome stesso del comando. Un comando
rifiutato non è un’eccezione: è un risultato con
outcome.status === "rejected" e problemi che dicono cosa correggere, che
l’esempio mostra in una riga di stato.
La pagina è servita con una Content Security Policy rigorosa: niente
<script> inline e niente attributi style=; script e stili vanno in file
separati.
5. Fatti la tua copia
Sezione intitolata “5. Fatti la tua copia”In un progetto generato con BACKEND juce che aggiunge shuffle_juce
(tutorial 2), copia main.cpp e la cartella page/ dall’esempio, poi:
shuffle_engine_add_app (MyShow BACKEND juce GUI SOURCES src/main.cpp USES juce_platform juce_webui ui_bridge setlist)juce_add_binary_data (MyShowPage HEADER_NAME WebUiPageData.h NAMESPACE WebUiPageData SOURCES page/index.html page/style.css page/app.js <path to shuffle_engine>/core/ui_bridge/web/shuffle_bridge.js)target_link_libraries (MyShow PRIVATE MyShowPage)Configura come nel tutorial 2 (shuffle_juce aggiunto dopo shuffle_engine). Su
Windows l’SDK di WebView2 deve essere trovato in fase di build
(JUCE_WEBVIEW2_PACKAGE_LOCATION, vedi
adapters/juce
in shuffle_juce); senza, il programma si compila e la vista segnala che non è
disponibile (l’esempio allora ripiega su --headless).
Prova una modifica: aggiungi un quarto brano nel costruttore di Engine (sia
in factory.addSong sia nella lista di show.open), ricompila e guardalo
comparire nella pagina senza toccare il JavaScript.
Cosa hai imparato
Sezione intitolata “Cosa hai imparato”- la pagina usa gli stessi comandi di ogni altra sorgente, con la stessa validazione, la stessa conferma e lo stesso annullamento;
- lo stato arriva alla pagina come topic: prima uno snapshot, poi piccoli delta;
- il client è generato dal catalogo, quindi pagina e applicazione non possono andare fuori sincrono.
Per saperne di più: Build a web UI in 10 minutes
(dichiarazioni TypeScript, stream per i meter, accessibilità),
Read state, Theme your UI.
Le pagine in un browser separato, telefono compreso, si connettono con il
trasporto WebSocket (shuffle_net_websocket,
esempio ui_bridge/websocket);
un binding WebAssembly è in arrivo (core/ui_bridge).
© 2026 Umberto Sorbo. Il software è distribuito con la Apache License, Version 2.0, e fornito “così com’è”, senza garanzie o condizioni di alcun tipo; leggi l’esclusione di responsabilità.
SHUFFLE Framework e il logo di SHUFFLE Framework sono marchi di Umberto Sorbo; la licenza non concede alcun diritto su di essi (marchi). Gli altri nomi appartengono ai rispettivi titolari. Caratteri: IBM Plex Sans e JetBrains Mono, SIL Open Font License 1.1.
Contatti: shuffleframework@gmail.com · Instagram @shuffleframework. Problemi di sicurezza: scrivi allo stesso indirizzo con “Security” nell’oggetto, non in una issue pubblica.