Ajouter des vidéos en mode Picture-in-picture (PIP)

Essayer Compose
Jetpack Compose est le kit d'outils d'interface utilisateur recommandé pour Android. Découvrez comment prendre en charge le mode Picture-in-picture dans Compose.

À partir d'Android 8.0 (niveau d'API 26), Android permet de lancer des activités en mode Picture-in-picture (PiP). Le mode Picture-in-picture est un type particulier de mode multifenêtre principalement utilisé pour la lecture vidéo. Elle permet à l'utilisateur de regarder une vidéo dans une petite fenêtre épinglée dans un coin de l'écran tout en naviguant entre les applications ou en parcourant le contenu de l'écran principal.

Le mode Picture-in-picture exploite les API multifenêtre disponibles dans Android 7.0 pour fournir la fenêtre de superposition vidéo épinglée. Pour ajouter le mode PiP à votre application, vous devez enregistrer vos activités compatibles avec le mode PiP, passer votre activité en mode PiP si nécessaire et vous assurer que les éléments de l'interface utilisateur sont masqués et que la lecture vidéo se poursuit lorsque l'activité est en mode PiP.

La fenêtre PiP s'affiche dans la couche supérieure de l'écran, dans un coin choisi par le système.

Le mode PIP est également pris en charge sur les appareils compatibles équipés de l'OS Android TV et équipés d'Android 14 (niveau d'API 34) ou version ultérieure. Bien qu'il existe de nombreuses similitudes, d'autres considérations doivent être prises en compte lors de l'utilisation du PIP sur un téléviseur.

Comment les utilisateurs peuvent-ils interagir avec la fenêtre PIP ?

Les utilisateurs peuvent faire glisser la fenêtre PiP vers un autre emplacement. À partir d'Android 12, les utilisateurs peuvent également:

  • Appuyez une seule fois sur la fenêtre pour afficher un bouton d'activation du plein écran, un bouton de fermeture, un bouton de paramètres et des actions personnalisées fournies par votre application (par exemple, les commandes de lecture).

  • Appuyez deux fois sur la fenêtre pour passer de la taille PIP actuelle à la taille PIP maximale ou minimale. Par exemple, appuyer deux fois sur une fenêtre agrandie la réduit. L'inverse est également vrai.

  • Fermez la fenêtre en la faisant glisser vers le bord gauche ou droit. Pour désenfouir la fenêtre, appuyez sur la partie visible de la fenêtre masquée ou faites-la glisser.

  • Redimensionnez la fenêtre PIP à l'aide du pincement pour zoomer.

Votre application contrôle le moment où l'activité en cours passe en mode PiP. Voici quelques exemples:

  • Une activité peut passer en mode PiP lorsque l'utilisateur appuie sur le bouton Accueil ou balaie l'écran vers le haut pour accéder à l'accueil. C'est ainsi que Google Maps continue d'afficher les itinéraires pendant que l'utilisateur exécute une autre activité en même temps.

  • Votre application peut passer une vidéo en mode PIP lorsque l'utilisateur revient en arrière depuis la vidéo pour parcourir d'autres contenus.

  • Votre application peut passer une vidéo en mode PIP pendant qu'un utilisateur regarde la fin d'un épisode de contenu. L'écran principal affiche des informations promotionnelles ou récapitulatives sur le prochain épisode de la série.

  • Votre application peut permettre aux utilisateurs de mettre des contenus supplémentaires en file d'attente pendant qu'ils regardent une vidéo. La vidéo continue de se lire en mode PIP, tandis que l'écran principal affiche une activité de sélection de contenu.

Déclarer la compatibilité avec le mode Picture-in-picture

Par défaut, le système n'est pas compatible automatiquement avec le mode PiP pour les applications. Si vous souhaitez prendre en charge le mode PiP dans votre application, enregistrez votre activité vidéo dans votre fichier manifeste en définissant android:supportsPictureInPicture sur true. Spécifiez également que votre activité gère les modifications de configuration de la mise en page afin qu'elle ne soit pas redémarrée lorsque des modifications de mise en page se produisent lors des transitions en mode PiP.

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

Passer en mode PiP

À partir d'Android 12, vous pouvez passer votre activité en mode PIP en définissant l'indicateur setAutoEnterEnabled sur true. Avec ce paramètre, une activité passe automatiquement en mode PiP si nécessaire, sans avoir à appeler explicitement enterPictureInPictureMode() dans onUserLeaveHint. Cela présente l'avantage de fournir des transitions bien plus fluides. Pour en savoir plus, consultez Faciliter les transitions vers le mode PiP à partir de la navigation par gestes.

Si vous ciblez Android 11 ou une version antérieure, une activité doit appeler enterPictureInPictureMode() pour passer en mode PIP. Par exemple, le code suivant met une activité en mode PiP lorsque l'utilisateur clique sur un bouton dédié dans l'UI de l'application :

Kotlin

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Java

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

Vous pouvez inclure une logique qui met une activité en mode PiP au lieu de passer en arrière-plan. Par exemple, Google Maps passe en mode PIP si l'utilisateur appuie sur le bouton "Accueil" ou "Récents" pendant la navigation dans l'application. Vous pouvez détecter ce cas en remplaçant onUserLeaveHint() :

Kotlin

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Java

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

Recommandation : Proposez aux utilisateurs une expérience de transition PiP soignée

Android 12 a apporté des améliorations esthétiques importantes aux transitions animées entre les fenêtres en plein écran et PiP. Nous vous recommandons vivement d'implémenter toutes les modifications applicables. Une fois que vous l'aurez fait, ces modifications s'adapteront automatiquement aux grands écrans tels que les appareils pliables et les tablettes, sans aucune autre action requise.

Si votre application n'inclut pas les mises à jour applicables, les transitions PiP restent fonctionnelles, mais les animations sont moins soignées. Par exemple, la transition du mode plein écran au mode PiP peut entraîner la disparition de la fenêtre PiP pendant la transition, avant qu'elle ne réapparaisse une fois la transition terminée.

Ces modifications impliquent les éléments suivants.

  • Amélioration des transitions vers le mode PiP à partir de la navigation par gestes
  • Définir un sourceRectHint approprié pour entrer et quitter le mode PiP
  • Désactiver le redimensionnement fluide pour les contenus autres que vidéo

Consultez l'exemple PictureInPicture Kotlin Android pour activer une expérience de transition soignée.

Améliorer les transitions vers le mode PIP à partir de la navigation par gestes

À partir d'Android 12, l'indicateur setAutoEnterEnabled fournit une animation beaucoup plus fluide pour passer au contenu vidéo en mode PIP à l'aide de la navigation par gestes (par exemple, lorsque vous balayez l'écran vers le haut pour accéder à l'accueil depuis l'écran plein écran).

Pour effectuer cette modification, procédez comme suit et consultez cet exemple pour obtenir des informations de référence:

  1. Utilisez setAutoEnterEnabled pour construire PictureInPictureParams.Builder:

    Kotlin

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Appelez setPictureInPictureParams avec la PictureInPictureParams à jour à l'avance. L'application n'attend pas le rappel onUserLeaveHint (comme elle l'aurait fait sous Android 11).

    Par exemple, vous pouvez appeler setPictureInPictureParams lors de la toute première lecture et lors de toute lecture suivante si le format est modifié.

  3. Appelez setAutoEnterEnabled(false), mais uniquement si nécessaire. Par exemple, vous ne souhaiterez probablement pas passer en mode PIP si la lecture en cours est mise en pause.

Définir un sourceRectHint approprié pour entrer et quitter le mode PiP

Depuis l'introduction du mode PiP dans Android 8.0, setSourceRectHint indique la zone de l'activité visible après la transition vers le mode Picture-in-picture (par exemple, les limites de la vue vidéo dans un lecteur vidéo).

Avec Android 12, le système utilise sourceRectHint pour implémenter une animation beaucoup plus fluide à la fois lors de l'entrée et de la sortie du mode PiP.

Pour définir correctement sourceRectHint pour entrer et quitter le mode PiP :

  1. Créez PictureInPictureParams en utilisant les limites appropriées comme sourceRectHint. Nous vous recommandons également d'associer un écouteur de changement de mise en page au lecteur vidéo:

    Kotlin

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    Java

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  2. Si nécessaire, mettez à jour sourceRectHint avant que le système ne démarre la transition de sortie. Lorsque le système est sur le point de quitter le mode PiP, la hiérarchie des vues de l'activité est mise en page selon sa configuration de destination (par exemple, en plein écran). L'application peut associer un écouteur de modification de mise en page à sa vue racine ou à sa vue cible (telle que la vue du lecteur vidéo) pour détecter l'événement et mettre à jour sourceRectHint avant le début de l'animation.

    Kotlin

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            val sourceRectHint = Rect()
            playerView.getGlobalVisibleRect(sourceRectHint)
            setPictureInPictureParams(
                PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build()
            )
        }
    }
    
    

    Java

    // Listener is called right after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                        oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft
            || right != oldRight
            || top != oldTop
            || bottom != oldBottom) {
            // The playerView's bounds changed, update the source hint rect to
            // reflect its new bounds.
            final Rect sourceRectHint = new Rect();
            playerView.getGlobalVisibleRect(sourceRectHint);
            setPictureInPictureParams(
                new PictureInPictureParams.Builder()
                    .setSourceRectHint(sourceRectHint)
                    .build());
        }
    });
    
    

Désactiver le redimensionnement fluide pour les contenus autres que vidéo

Android 12 ajoute l'indicateur setSeamlessResizeEnabled, qui fournit une animation en fondu enchaîné beaucoup plus fluide lors du redimensionnement d'un contenu autre que vidéo dans la fenêtre PIP. Auparavant, le redimensionnement d'un contenu autre que vidéo dans une fenêtre PIP pouvait créer des artefacts visuels choquants.

Pour désactiver le redimensionnement fluide pour les contenus autres que vidéo:

Kotlin

setPictureInPictureParams(PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build())

Java

setPictureInPictureParams(new PictureInPictureParams.Builder()
    .setSeamlessResizeEnabled(false)
    .build());

Gérer l'UI en mode PIP

Lorsque l'activité passe en mode Picture-in-picture (PIP) ou en quitte, le système appelle Activity.onPictureInPictureModeChanged() ou Fragment.onPictureInPictureModeChanged().

Android 15 introduit des modifications qui garantissent une transition encore plus fluide lorsque vous accédez au mode PiP. Cela est utile pour les applications dont les éléments d'interface utilisateur sont superposés à leur interface utilisateur principale, qui passe en mode PiP.

Les développeurs utilisent le rappel onPictureInPictureModeChanged() pour définir une logique qui active/désactive la visibilité des éléments d'interface utilisateur superposés. Ce rappel est déclenché lorsque l'animation de sortie ou d'entrée en mode PiP est terminée. À partir d'Android 15, la classe PictureInPictureUiState inclut un nouvel état.

Avec ce nouvel état d'interface utilisateur, les applications ciblant Android 15 observent le rappel Activity#onPictureInPictureUiStateChanged() appelé avec isTransitioningToPip() dès le début de l'animation PIP. De nombreux éléments d'interface utilisateur ne sont pas pertinents pour l'application lorsqu'elle est en mode PiP. Par exemple, les vues ou la mise en page qui incluent des informations telles que des suggestions, des vidéos à venir, des notes et des titres. Lorsque l'application passe en mode PiP, utilisez le rappel onPictureInPictureUiStateChanged() pour masquer ces éléments d'interface utilisateur. Lorsque l'application passe en mode plein écran à partir de la fenêtre PIP, utilisez le rappel onPictureInPictureModeChanged() pour afficher ces éléments, comme illustré dans les exemples suivants:

Kotlin

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Cette option d'activation/de désactivation rapide de la visibilité des éléments d'interface utilisateur non pertinents (pour une fenêtre PiP) permet d'assurer une animation d'entrée PiP plus fluide et sans scintillement.

Forcez ces rappels pour redessiner les éléments d'interface utilisateur de l'activité. N'oubliez pas que, en mode PiP, votre activité s'affiche dans une petite fenêtre. Les utilisateurs ne peuvent pas interagir avec les éléments d'interface utilisateur de votre application lorsque celle-ci est en mode PiP, et les détails des petits éléments d'interface utilisateur peuvent être difficiles à voir. Les activités de lecture de vidéos avec une interface utilisateur minimale offrent une expérience utilisateur optimale.

Si votre application doit fournir des actions personnalisées pour le mode PiP, consultez la section Ajouter des commandes sur cette page. Supprimez les autres éléments d'interface utilisateur avant que votre activité ne passe en mode PiP, puis restaurez-les lorsque votre activité redevient en plein écran.

Ajouter des commandes

La fenêtre PiP peut afficher des commandes lorsque l'utilisateur ouvre le menu de la fenêtre (en appuyant sur la fenêtre sur un appareil mobile ou en sélectionnant le menu depuis la télécommande du téléviseur).

Si une application dispose d'une session multimédia active, les commandes de lecture, de mise en pause, de suivi et de précédent s'affichent.

Vous pouvez également spécifier explicitement des actions personnalisées en compilant PictureInPictureParams avec PictureInPictureParams.Builder.setActions() avant de passer en mode PIP, puis en transmettant les paramètres lorsque vous passez en mode PIP à l'aide de enterPictureInPictureMode(android.app.PictureInPictureParams) ou de setPictureInPictureParams(android.app.PictureInPictureParams). Soyez prudent. Si vous essayez d'ajouter plus de getMaxNumPictureInPictureActions(), vous n'obtiendrez que le nombre maximal.

Poursuivre la lecture d'une vidéo en mode PIP

Lorsque votre activité passe en mode PiP, le système place l'activité en état suspendu et appelle la méthode onPause() de l'activité. La lecture vidéo ne doit pas être mise en pause et doit continuer à être diffusée si l'activité est mise en pause lors de la transition vers le mode PIP.

Sous Android 7.0 et versions ultérieures, vous devez suspendre et reprendre la lecture vidéo lorsque le système appelle onStop() et onStart() de votre activité. Vous évitez ainsi de vérifier si votre application est en mode PiP dans onPause() et de poursuivre la lecture explicitement.

Si vous n'avez pas défini l'indicateur setAutoEnterEnabled sur true et que vous devez suspendre la lecture dans votre implémentation onPause(), recherchez le mode PiP en appelant isInPictureInPictureMode() et gérez la lecture de manière appropriée. Exemple :

Kotlin

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode) {
        // Continue playback.
    } else {
        // Use existing playback logic for paused activity behavior.
    }
}

Java

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback.
    if (isInPictureInPictureMode()) {
        // Continue playback.
        ...
    } else {
        // Use existing playback logic for paused activity behavior.
        ...
    }
}

Lorsque votre activité passe du mode PiP au mode plein écran, le système reprend votre activité et appelle votre méthode onResume().

Utiliser une seule activité de lecture pour le mode PiP

Dans votre application, un utilisateur peut sélectionner une nouvelle vidéo lorsqu'il recherche du contenu sur l'écran principal, alors qu'une activité de lecture vidéo est en mode PIP. Lancez la nouvelle vidéo dans l'activité de lecture existante en mode plein écran, au lieu de lancer une nouvelle activité qui pourrait prêter à confusion.

Pour vous assurer qu'une seule activité est utilisée pour les requêtes de lecture vidéo et que le mode PIP est activé ou désactivé selon les besoins, définissez le android:launchMode de l'activité sur singleTask dans votre fichier manifeste:

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

Dans votre activité, remplacez onNewIntent() et gérez la nouvelle vidéo, en arrêtant toute lecture vidéo existante si nécessaire.

Bonnes pratiques

Le mode PiP peut être désactivé sur les appareils disposant de peu de RAM. Avant que votre application utilise le mode PiP, vérifiez qu'il est disponible en appelant hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Le mode PiP est destiné aux activités qui lisent des vidéos en plein écran. Lorsque vous passez en mode PIP, évitez d'afficher autre chose que du contenu vidéo. Suivez le passage de votre activité en mode PIP et masquez les éléments d'interface utilisateur, comme décrit dans la section Gérer l'interface utilisateur pendant le mode PIP.

Lorsqu'une activité est en mode PiP, elle ne reçoit pas par défaut la sélection de l'entrée. Pour recevoir des événements d'entrée en mode PiP, utilisez MediaSession.setCallback(). Pour en savoir plus sur l'utilisation de setCallback(), consultez la section Afficher une carte "En écoute".

Lorsque votre application est en mode PIP, la lecture vidéo dans la fenêtre PIP peut provoquer des interférences audio avec une autre application, comme un lecteur de musique ou une application de recherche vocale. Pour éviter cela, demandez la priorité audio lorsque vous commencez à lire la vidéo et gérez les notifications de modification de la priorité audio, comme décrit dans la section Gérer la priorité audio. Si vous recevez une notification de perte de la focalisation audio en mode PIP, mettez en pause ou arrêtez la lecture vidéo.

Lorsque votre application est sur le point d'entrer en mode PiP, notez que seule l'activité supérieure passe en mode Picture-in-picture. Dans certains cas, par exemple sur les appareils multifenêtres, il est possible que l'activité ci-dessous soit désormais affichée et redevienne visible en même temps que l'activité PIP. Vous devez gérer ce cas en conséquence, y compris l'activité ci-dessous qui reçoit un rappel onResume() ou onPause(). Il est également possible que l'utilisateur interagisse avec l'activité. Par exemple, si une activité de liste de vidéos s'affiche et que l'activité de lecture de la vidéo est en mode PiP, l'utilisateur peut sélectionner une nouvelle vidéo dans la liste et l'activité PiP doit être mise à jour en conséquence.

Exemples de code supplémentaires

Pour télécharger un exemple d'application écrite en Kotlin, consultez Exemple Android PictureInPicture (Kotlin).