432 lines
11 KiB
Markdown
432 lines
11 KiB
Markdown
# 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.
|