# Plugin `plugins/google_cast` – Come funziona Questo documento descrive **l’architettura** e il **flusso di funzionamento** del plugin `plugins/google_cast` del progetto, sulla base della struttura riportata e dei file analizzati durante il debug. --- ## 1. Obiettivo del plugin Il plugin `google_cast` fa da ponte tra Flutter/Dart e il layer nativo Android per gestire: - **Google Cast standard** (Chromecast / Android TV con receiver standard) - **Custom Receiver** (receiver web / CAF) - il dialog nativo di selezione device - comandi media (play, pause, seek, queue, volume, mute) - eventi di ritorno verso Flutter (cast connesso, cast fermato, cambio item queue, errori, ecc.) In pratica: - **Flutter** decide *quando* connettersi e *quale media* mandare - **il plugin Android** parla con le API Google Cast - **il receiver** (standard o custom) riproduce davvero i contenuti sul TV/device Cast --- ## 2. Struttura del plugin ```text plugins/google_cast |-- android | |-- build.gradle.kts | |-- settings.gradle.kts | `-- src/main/kotlin/com/aves/google_cast | |-- CastActivityProvider.kt | |-- CastOptionsProvider.kt | |-- FlutterCastFrameworkPlugin.kt | `-- cast | |-- CastContextManager.kt | |-- CastCustomReceiver.kt | |-- CastDialogLauncher.kt | |-- CastMediaClient.kt | |-- CastQueueManager.kt | `-- CastSessionHandler.kt |-- lib | `-- google_cast.dart `-- pubspec.yaml ``` --- ## 3. Ruolo dei file principali ### `lib/google_cast.dart` È l’entry point Dart del package/plugin. In molti plugin Flutter questo file fa una o più di queste cose: - espone API pubbliche al codice Dart - definisce wrapper leggeri verso il `MethodChannel` - centralizza eventuali modelli / helper lato Flutter Nel tuo progetto, il vero orchestratore lato app non è qui ma nei controller di app (`cast_controller.dart`, `google_cast_controller.dart`, `custom_cast_controller.dart`). --- ### `android/src/main/kotlin/com/aves/google_cast/FlutterCastFrameworkPlugin.kt` È il file **principale del plugin Flutter lato Android**. #### Responsabilità - registra il `MethodChannel('google_cast')` - riceve le chiamate da Flutter (`showCastDialog`, `loadQueue`, `play`, `pause`, `seekTo`, `getVolume`, ecc.) - smista ogni metodo verso i componenti modulari del package `cast/` - aggancia il plugin al ciclo di vita dell’`Activity` - inizializza i componenti principali del Cast: - `CastContextManager` - `CastSessionHandler` - `CastMediaClient` - `CastQueueManager` - `CastDialogLauncher` - `CastCustomReceiver` È il **router centrale** del plugin. --- ### `CastContextManager.kt` Gestisce il **CastContext** e l’accesso alla sessione corrente. #### In pratica gestisce: - l’`Activity` attuale - l’inizializzazione di `CastContext` - il recupero della sessione Cast attiva - il recupero del `RemoteMediaClient` #### Metodi tipici/attesi - `attachActivity(activity)` - `detachActivity()` - `ensureCastContext()` - `getSession()` - `getClient()` È il punto base da cui gli altri oggetti ottengono la sessione Cast corrente e il media client corrente. --- ### `CastSessionHandler.kt` Gestisce gli **eventi di sessione Cast**. #### Di solito si occupa di: - `onSessionStarting` - `onSessionStarted` - `onSessionResumed` - `onSessionEnded` - invio eventi verso Flutter, ad esempio: - `onCastReady` - `onCastStopped` Nel tuo flusso attuale, `onCastReady` viene usato per segnalare una **sessione connessa**, ma **non necessariamente** un media già caricato. --- ### `CastDialogLauncher.kt` Si occupa di aprire il dialog nativo di scelta dispositivo Cast. #### Tipicamente fa: - crea/configura un `MediaRouteButton` - invoca il bottone o mostra il picker Cast - consente all’utente di scegliere il device Quando Flutter chiama `showCastDialog`, il plugin passa il comando a `CastDialogLauncher`, che mostra il picker dispositivo. --- ### `CastMediaClient.kt` Gestisce i **comandi media** e il **callback del `RemoteMediaClient`**. #### Responsabilità principali - `play()` - `pause()` - `seek(position)` - `getMediaStatus()` - `getVolume()` - `setVolume(v)` - `setMute(m)` - registrazione del callback sul `RemoteMediaClient` #### Perché è cruciale Fa due cose fondamentali: 1. **Comandi verso Google Cast** - play / pause - seek - volume / mute 2. **Eventi da Google Cast verso Flutter** - `onQueueIndexChanged` - `onVideoFinished` - `onCastError` È fondamentale per slideshow e sincronizzazione UI. --- ### `CastQueueManager.kt` Gestisce la **queue dei media** Cast. #### Responsabilità tipiche - `loadQueue(items, startIndex)` - `next()` - `prev()` - costruzione di `MediaQueueItem` - associazione metadati per item (titolo, mimeType, flags foto/video, customData) È il file che determina come una lista Flutter di media diventa una vera queue Google Cast. --- ### `CastCustomReceiver.kt` Gestisce il **Custom Receiver CAF / Web Receiver**. #### Responsabilità - avvio del receiver custom con `receiverUrl` - invio messaggi JSON al receiver web - ricezione/gestione del namespace custom (se implementato) Nel tuo progetto, Flutter costruisce un URL tipo: ```text https://.../cast_receiver/receiver.html?ws=... ``` e poi chiama `startCustomReceiver`. --- ### `CastActivityProvider.kt` Aiuta a dare accesso all’`Activity` giusta o a integrare il plugin con l’host Android. --- ### `CastOptionsProvider.kt` È il provider di configurazione Google Cast lato Android. #### Tipicamente definisce: - receiver application ID di default - eventuali opzioni del framework Cast --- ## 4. Flusso end-to-end: Google Cast standard ### A. Connessione del device 1. Flutter preme l’icona Cast 2. chiama `showCastDialog` 3. `FlutterCastFrameworkPlugin.kt` inoltra a `CastDialogLauncher.kt` 4. l’utente sceglie il device 5. `CastSessionHandler.kt` riceve `onSessionStarted` 6. il plugin manda a Flutter `onCastReady` ### B. Caricamento media Quando l’utente preme: - cast manuale - fullscreen cast - slideshow Flutter costruisce una queue e chiama `loadQueue`. Il plugin passa il tutto a `CastQueueManager.loadQueue(...)`, che crea i `MediaQueueItem` e li carica nel `RemoteMediaClient`. ### C. Controlli playback Dai controller Flutter partono metodi come: - `playCast()` - `pauseCast()` - `seekCast()` - `nextManual()` - `prevManual()` - `setCastVolume()` - `toggleMuteCast()` Questi passano dal `MethodChannel`, arrivano a `FlutterCastFrameworkPlugin.kt` e poi vengono gestiti da: - `CastMediaClient.kt` - `CastQueueManager.kt` ### D. Eventi di ritorno verso Flutter Dal native tornano eventi come: - `onCastReady` - `onCastStopped` - `onQueueIndexChanged` - `onVideoFinished` - `onCastError` Questi aggiornano in Flutter: - icona Cast - indice corrente - stato play/pause - posizione/durata - slideshow timer foto/video --- ## 5. Flusso end-to-end: Custom Receiver ### A. Connessione/sessione Flutter chiama: - `showCastDialog` - `startCustomReceiver(appId, receiverUrl)` Il plugin passa a `CastCustomReceiver.kt`, che avvia il receiver web. ### B. Caricamento media Flutter non usa `loadQueue` standard, ma manda messaggi custom JSON al receiver, ad esempio: - `LOAD_QUEUE` - `PLAY` - `PAUSE` - `SEEK` - `NEXT` - `PREV` - `SET_VOLUME` - `SET_MUTE` - `GET_VOLUME` - `GET_MEDIA_STATUS` ### C. Eventi verso Flutter Il receiver web risponde con messaggi come: - `MEDIA_STATUS` - `VOLUME_STATUS` - `CAST_READY` - `CAST_STOPPED` che il `CustomCastController` usa per aggiornare la UI Flutter. --- ## 6. Differenza tra Google Cast standard e Custom ### Google Cast standard - il volume è nativo via `CastSession` - la queue è gestita dal framework Cast - il passaggio video → next è naturale se la queue è corretta - `RemoteMediaClient.Callback` è fondamentale ### Custom Receiver - il volume ha senso **solo se il receiver web lo implementa davvero** - le foto/video vengono gestiti dalla tua logica JS/CAF - gli eventi stato/volume dipendono dal receiver - è più flessibile ma più facile da rompere --- ## 7. Punto delicato emerso nel debug Durante il refactor si è visto che una distinzione è fondamentale: ## **Connettere il device != aver già caricato un media** Quindi il comportamento corretto è: - `onCastReady` = sessione pronta - **NO polling immediato** se la queue è ancora vuota - il polling deve partire solo dopo `loadQueue` Altrimenti il plugin rischia di generare: - `Invalid Request` - `MediaQueue error` - `IDLE_REASON_ERROR` --- ## 8. Responsabilità lato Flutter (fuori dal plugin ma essenziali) Anche se non sono dentro `plugins/google_cast`, lato app ci sono controller importanti: - `cast_controller.dart` → controller unificato backend - `google_cast_controller.dart` → orchestrazione Google standard - `custom_cast_controller.dart` → orchestrazione custom receiver Questi file decidono: - quando aprire il dialog Cast - quando connettere il device - quando mandare davvero una queue - quando mostrare controlli / slideshow / fullscreen viewer Il plugin Android da solo non basta: è il layer Dart che decide il flusso UX. --- ## 9. Mappa rapida responsabilità ### Flutter / UI - pulsante Cast - selection mode - slideshow/manual/fullscreen - bottom sheet controlli ### `cast_controller.dart` - facade unificata Google / Custom / DLNA ### `google_cast_controller.dart` - queue standard Cast - slideshow foto/video lato Flutter - polling media/volume ### `custom_cast_controller.dart` - invio messaggi JSON al receiver custom - stato custom receiver ### `FlutterCastFrameworkPlugin.kt` - router MethodChannel lato Android ### `CastContextManager.kt` - CastContext + session + media client ### `CastSessionHandler.kt` - session lifecycle → eventi verso Flutter ### `CastQueueManager.kt` - queue standard Google Cast ### `CastMediaClient.kt` - play/pause/seek/volume + callback eventi media ### `CastCustomReceiver.kt` - custom receiver CAF/web ### `CastDialogLauncher.kt` - dialog selezione device --- ## 10. Se devi debuggare il plugin in futuro Ordine consigliato per capire dove si rompe qualcosa: ### Se non si connette il device - `FlutterCastFrameworkPlugin.kt` - `CastDialogLauncher.kt` - `CastSessionHandler.kt` - `CastContextManager.kt` ### Se si connette ma non parte il media - `CastQueueManager.kt` - `CastMediaClient.kt` - `google_cast_controller.dart` ### Se slideshow si ferma o salta male foto/video - `google_cast_controller.dart` - `CastMediaClient.kt` (callback `onQueueIndexChanged`) - eventualmente `CastQueueManager.kt` ### Se il Custom receiver ha problemi UI / progress / volume - `CastCustomReceiver.kt` - receiver web (`receiver.html`, JS) - `custom_cast_controller.dart` ### Se il volume non si aggiorna - `CastMediaClient.kt` - `google_cast_controller.dart` - widget controlli Flutter (`cast_controls_sheet.dart`, fullscreen viewer) --- ## 11. Conclusione Il plugin `google_cast` del progetto è organizzato in modo modulare e separa bene le responsabilità: - il file principale `FlutterCastFrameworkPlugin.kt` riceve i comandi Flutter - il package `cast/` contiene i moduli Android specializzati - i controller Flutter coordinano UX, queue e sincronizzazione stato Il punto più delicato dell’architettura è la distinzione tra: - **sessione Cast connessa** - **media realmente caricato sulla queue** Capire bene questa differenza evita gran parte dei bug visti in debug.