Utiliser la bibliothèque Jetpack Picture-in-Picture

La bibliothèque Jetpack Picture-in-picture (PiP) offre une solution simplifiée et robuste aux développeurs d'applications Android pour implémenter la fonctionnalité PiP, en particulier pour les applications de lecture multimédia, de communication vidéo et de navigation. En fournissant une API unifiée, la bibliothèque permet d'éliminer le code récurrent et les bugs courants dans l'application, et d'améliorer la qualité globale de l'expérience utilisateur en mode Picture-in-picture.

La bibliothèque Jetpack PiP facilite l'utilisation des API PiP existantes en résolvant plusieurs problèmes et incohérences clés dans l'écosystème Android :

  • Fragmentation de l'OS : la bibliothèque gère automatiquement les différences dans les appels d'API PiP entre les différentes versions d'Android, par exemple en utilisant enterPictureInPictureMode avant Android 12 et isAutoEnterEnabled après. Les développeurs n'ont donc pas besoin de gérer les différences de version.
  • Paramètres PiP incorrects : il fournit une solution unifiée pour définir correctement les paramètres PiP, par exemple setSourceRectHint, afin de créer des animations fluides et de haute qualité pendant la lecture de contenus multimédias.
  • Rappels d'état PiP unifiés : il regroupe onPictureInPictureModeChanged et onPictureInPictureUiStateChanged dans une seule interface de rappel unifiée (PictureInPictureDelegate.OnPictureInPictureEventListener) pour simplifier la gestion de l'état et de l'UI.
  • Réduction du code récurrent : la bibliothèque réduit la quantité de code récurrent répétitif en proposant des ensembles prédéfinis de RemoteActions pour les cas d'utilisation courants, tels que les commandes de lecture et les actions d'appel vidéo.
  • Pérennité : d'autres fonctionnalités PiP sont fournies via la bibliothèque Jetpack, ce qui permet aux utilisateurs d'accéder à des fonctionnalités supplémentaires avec un minimum d'effort.

Workflow de migration

Identifiez la catégorie de cas d'utilisation de l'application et l'ancienne logique PiP :

Catégories : lecture vidéo, navigation ou appel vidéo.

Ancienne logique PiP à identifier :

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Configuration d'AndroidManifest

Assurez-vous que l'activité qui passe en mode PIP déclare la compatibilité dans AndroidManifest.xml avec le configChanges nécessaire pour éviter les redémarrages inutiles :

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

3. Configuration de l'environnement

Ajoutez les dépendances requises à 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") }

Utilisez les dernières bibliothèques AndroidX pour les dépendances et consultez la page des versions pour en savoir plus.

4. Sélection et initialisation du modèle

Choisissez le modèle d'implémentation qui correspond le mieux au cas d'utilisation de l'application :

  • Navigation et appel vidéo : BasicPictureInPicture ; le redimensionnement fluide n'est généralement pas pris en charge, et vous n'avez pas besoin d'un indice de rectangle source.
  • Lecture vidéo : VideoPlaybackPictureInPicture suit automatiquement les limites de la vue du lecteur pour l'indice de rectangle source et permet un redimensionnement fluide par défaut.

Pour adopter la bibliothèque Jetpack, remplacez votre implémentation PiP personnalisée existante par les API de la bibliothèque Jetpack. La complexité et le coût de l'adoption varient en fonction de l'implémentation actuelle de l'application.

Les sections suivantes décrivent certains des cas d'utilisation types du Picture-in-picture et les étapes de mise en œuvre nécessaires :

L'application informe la bibliothèque de l'état actif ou inactif de la navigation et définit le format. La bibliothèque Jetpack gère le reste.

Différences majeures :

  1. Il n'est pas nécessaire de différencier l'entrée automatique de l'ancienne entrée côté application.
  2. Interfaces de rappel consolidées.
  3. Nouveau compilateur PictureInPictureParams pour la rétrocompatibilité.

Appel vidéo

L'application informe la bibliothèque de l'état actif ou inactif de l'appel et définit le format.

Différences majeures :

  1. Il n'est pas nécessaire de différencier l'entrée automatique de l'ancienne entrée côté application.
  2. Interfaces de rappel consolidées.
  3. Nouveau compilateur PictureInPictureParams pour la rétrocompatibilité.
  4. Icônes d'action standardisées pour les appels vidéo.

5. Migration de code

  • Logique d'entrée : remplacez la logique spécifique à l'API, telle que setAutoEnterEnabled pour Android 12 et versions ultérieures, ou onUserLeaveHint pour Android 11 et versions antérieures, par setEnabled. Déclenchez cette action chaque fois que l'état d'éligibilité au mode PiP change.
  • Rappels : regroupez onPictureInPictureModeChanged (activation/désactivation de la mise en page) et onPictureInPictureUiStateChanged (animation/états) dans un rappel unifié basé sur les événements onPictureInPictureEvent.
  • Actions et paramètres : mettez à jour les paramètres à l'aide de setActions et setAspectRatio sur l'instance de modèle chaque fois qu'ils changent.

Modèles d'implémentation de référence

Exemples d'implémentations.

Navigation et appel vidéo

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 */ }
        }
    }
}

Lecture de vidéos

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()
            }
        }
    }
}