Utilizzare la libreria Jetpack Picture in picture

La libreria Jetpack Picture-in-Picture (PiP) offre una soluzione semplificata e solida per gli sviluppatori di app per Android per implementare la funzionalità PiP, in particolare per le app di riproduzione multimediale, comunicazione video e navigazione. Fornendo un'API unificata, la libreria contribuisce a eliminare il codice boilerplate, i bug comuni nelle app e a migliorare la qualità complessiva dell'esperienza utente PiP.

La libreria Jetpack PiP facilita le API PiP esistenti risolvendo diverse sfide e incongruenze chiave nell'ecosistema Android:

  • Frammentazione del sistema operativo: la libreria gestisce automaticamente le differenze nelle chiamate API PiP tra le varie versioni di Android, ad esempio utilizzando enterPictureInPictureMode prima di Android 12 e isAutoEnterEnabled dopo, in modo che gli sviluppatori non debbano gestire le differenze di versione.
  • Parametri PiP errati: fornisce una soluzione unificata per impostare correttamente i parametri PiP, ad esempio setSourceRectHint, per creare animazioni fluide e di alta qualità durante la riproduzione multimediale.
  • Callback di stato PiP unificati: consolida onPictureInPictureModeChanged e onPictureInPictureUiStateChanged in un'unica interfaccia di callback unificata (PictureInPictureDelegate.OnPictureInPictureEventListener) per la gestione semplificata dello stato e dell'UI.
  • Riduzione del codice boilerplate: la libreria riduce la quantità di codice boilerplate ripetitivo offrendo set predefiniti di RemoteActions per i casi d'uso comuni, come i controlli di riproduzione e le azioni di videochiamata.
  • Protezione per il futuro: altre funzionalità PiP vengono fornite tramite la libreria Jetpack, consentendo agli utenti di accedere a funzionalità aggiuntive con uno sforzo minimo o nullo.

Workflow di migrazione

Identifica la categoria del caso d'uso dell'app e la logica PiP legacy:

Categorie: riproduzione video, navigazione o videochiamata.

Logica PiP legacy da identificare:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Configurazione di AndroidManifest

Assicurati che l'attività che entra in PiP dichiari il supporto in AndroidManifest.xml con le configChanges necessarie per evitare riavvii non necessari:

<activity
android:name="VideoActivity" android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>

3. Configurazione dell'ambiente

Aggiungi le dipendenze richieste a build.gradle:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Utilizza le librerie AndroidX più recenti per le dipendenze e consulta la pagina delle release per queste informazioni.

4. Selezione e inizializzazione del modello

Scegli il modello di implementazione più adatto al caso d'uso dell'app:

  • Navigazione e videochiamata: BasicPictureInPicture; in genere il ridimensionamento continuo non è supportato e non è necessario un suggerimento per il rettangolo di origine.
  • Riproduzione video: VideoPlaybackPictureInPicture; monitora automaticamente i limiti della visualizzazione del player per il suggerimento del rettangolo di origine e attiva il ridimensionamento continuo per impostazione predefinita.

Per adottare la libreria Jetpack, sostituisci l'implementazione PiP personalizzata esistente con le API della libreria Jetpack. La complessità e il costo dell'adozione variano in base all'implementazione attuale dell'app.

Le seguenti sezioni descrivono alcuni dei casi d'uso tipici di PiP e i passaggi di implementazione necessari:

L'app informa la libreria dello stato attivo o inattivo della navigazione e imposta le proporzioni. Al resto ci pensa la libreria Jetpack.

Differenze principali:

  1. Non è necessario distinguere l'inserimento automatico e l'inserimento legacy sul lato dell'app.
  2. Interfacce di callback consolidate.
  3. Nuovo builder PictureInPictureParams per la compatibilità con le versioni precedenti.

Videochiamata

L'app informa la libreria dello stato attivo o inattivo della chiamata e imposta le proporzioni.

Differenze principali:

  1. Non è necessario distinguere l'inserimento automatico e l'inserimento legacy sul lato dell'app.
  2. Interfacce di callback consolidate.
  3. Nuovo builder PictureInPictureParams per la compatibilità con le versioni precedenti.
  4. Icone di azione standardizzate per le videochiamate.

5. Migrazione del codice

  • Logica di inserimento: sostituisci la logica specifica dell'API, ad esempio setAutoEnterEnabled per Android 12 e versioni successive o onUserLeaveHint per Android 11 e versioni precedenti con setEnabled. Attiva questa opzione ogni volta che cambia lo stato di idoneità PiP.
  • Callback: consolida onPictureInPictureModeChanged (attivazione/disattivazione del layout) e onPictureInPictureUiStateChanged (animazione/stati) in un callback unificato basato su eventi onPictureInPictureEvent.
  • Azioni e parametri: aggiorna i parametri utilizzando setActions e setAspectRatio nell'istanza del modello ogni volta che cambiano.
  • Gestione speciale dei video: per le app video, utilizza setPlayerView per automatizzare gli aggiornamenti dei suggerimenti del rettangolo di origine e garantire transizioni fluide. ` ### 6. Esegui la pulizia

Per VideoPlaybackPictureInPicture, chiama close in onDispose o onDestroy per rilasciare le risorse come i tracker di visualizzazione.

Pattern di implementazione dei riferimenti

Esempi di implementazioni.

Navigazione e videochiamata

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Riproduzione video

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}