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

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

PIP exploite les API multifenêtres 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 en mode PIP si nécessaire, et vous assurer que les éléments de l'interface utilisateur sont masqués et que la lecture de la vidéo se poursuit lorsque l'activité est en mode PIP.

La fenêtre PIP apparaît dans la couche supérieure de l'écran, dans un coin choisi par le système.

Comment les utilisateurs peuvent 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 fois sur la fenêtre pour afficher un bouton d'activation et de fermeture en 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 basculer entre la taille PIP actuelle et la taille PIP maximale ou minimale. Par exemple, si vous appuyez deux fois sur une fenêtre agrandie, celle-ci est réduite, et l'inverse est également vrai.

  • Masquez la fenêtre en la faisant glisser vers le bord gauche ou droit. Pour la retirer, appuyez sur la partie visible de la cachette ou faites-la glisser vers l'extérieur.

  • Redimensionnez la fenêtre PIP en pinçant 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 d'accueil ou balaie l'écran vers le haut pour accéder à l'accueil. C'est ainsi que Google Maps continue d'afficher l'itinéraire lorsque l'utilisateur exécute une autre activité au même moment.

  • Votre application peut faire passer une vidéo en mode PIP lorsque l'utilisateur y retourne pour parcourir d'autres contenus.

  • Votre application peut faire 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 en file d'attente des contenus supplémentaires pendant qu'ils regardent une vidéo. La lecture de la vidéo se poursuit en mode PIP pendant que l'écran principal affiche une activité de sélection de contenu.

Déclarer la compatibilité avec le PIP

Par défaut, le système ne prend pas automatiquement en charge le mode PIP pour les applications. Si vous souhaitez prendre en charge le 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 relancée lorsque la mise en page change lors des transitions en mode PIP.

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

Passer en mode PIP pour votre activité

À 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. De plus, les transitions sont beaucoup plus fluides. Pour en savoir plus, consultez Rendre les transitions vers le mode PIP plus fluides depuis 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 fait passer une activité en mode PIP lorsque l'utilisateur clique sur un bouton dédié dans l'interface utilisateur 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 fait passer 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 d'accueil ou le bouton "Récents" pendant la navigation dans l'application. Vous pouvez intercepter ce cas de figure en remplaçant onUserLeaveHint():

Kotlin

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

Java

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

Recommandé: offrir aux utilisateurs une expérience de transition PIP soignée

Android 12 a apporté d'importantes améliorations esthétiques aux transitions animées entre les fenêtres plein écran et PIP. Nous vous recommandons vivement de mettre en œuvre toutes les modifications applicables. Une fois cela fait, ces modifications s'adaptent automatiquement aux grands écrans, tels que les appareils pliables et les tablettes, sans autre action de votre part.

Si votre application n'inclut pas de mises à jour applicables, les transitions PIP restent fonctionnelles, mais les animations sont moins soignées. Par exemple, le passage 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 actions suivantes :

  • Rendre les transitions vers le mode PIP plus fluides depuis la navigation par gestes
  • Définir un sourceRectHint approprié pour activer et quitter le mode PIP
  • Désactiver le redimensionnement aisé pour les contenus autres que vidéo

Reportez-vous à l'exemple PictureInPicture pour Android Kotlin comme référence pour faciliter la transition.

Rendre les transitions vers le mode PIP plus fluides depuis la navigation par gestes

À partir d'Android 12, l'indicateur setAutoEnterEnabled offre 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 revenir à l'accueil depuis le plein écran.

Pour effectuer cette modification, procédez comme suit et reportez-vous à cet exemple pour 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 au plus tôt avec la PictureInPictureParams à jour. L'application n'attend pas le rappel onUserLeaveHint (comme elle l'aurait fait dans Android 11).

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

  3. Appelez setAutoEnterEnabled(false), mais seulement lorsque cela est nécessaire. Par exemple, vous ne souhaitez probablement pas utiliser le PIP si la lecture en cours est en pause.

Définissez une valeur sourceRectHint appropriée pour activer et désactiver le mode PIP.

Depuis l'introduction du PIP dans Android 8.0, setSourceRectHint indique la zone de l'activité visible après la transition en 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 lors de l'entrée et de la sortie du mode PIP.

Pour définir correctement sourceRectHint afin d'activer et de quitter le mode PIP:

  1. Créez PictureInPictureParams en utilisant les limites appropriées comme sourceRectHint. Nous vous recommandons également de joindre un écouteur de modification 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 lance 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 définie dans 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 (par exemple, 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 aisé pour les contenus autres que vidéo

Android 12 ajoute l'indicateur setSeamlessResizeEnabled, qui fournit une animation de fondu enchaîné beaucoup plus fluide lorsque vous redimensionnez le contenu non 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 simple 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'interface utilisateur en mode PIP

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

Vous devez ignorer ces rappels pour redessiner les éléments d'interface utilisateur de l'activité. N'oubliez pas qu'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 lorsqu'elle est en mode PIP. Les détails des petits éléments d'interface peuvent être difficiles à voir. Les activités de lecture de vidéos avec une interface utilisateur minimale offrent la meilleure expérience utilisateur.

Si votre application doit fournir des actions personnalisées pour PIP, consultez la section Ajouter des commandes sur cette page. Supprimez les autres éléments d'interface utilisateur avant que l'activité ne passe dans le mode PIP, puis restaurez-les lorsqu'elle s'affiche à nouveau en plein écran:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
    }
}

Java

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in PiP mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

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 de la télécommande du téléviseur).

Si une application a une session multimédia active, les commandes lecture, pause, suivante et précédent s'affichent.

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

Poursuite de la lecture de la vidéo en mode PIP

Lorsque votre activité passe en mode PIP, le système la met en pause et appelle la méthode onPause() de l'activité. La lecture de la vidéo ne doit pas être mise en pause et doit reprendre la lecture si l'activité est mise en pause en mode PIP.

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

Si vous n'avez pas défini l'indicateur setAutoEnterEnabled sur true et que vous devez suspendre la lecture dans votre implémentation de onPause(), vérifiez le mode PIP en appelant isInPictureInPictureMode() et gérez la lecture de manière appropriée. Par 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é quitte le mode PIP de nouveau en mode plein écran, le système la reprend et appelle votre méthode onResume().

Utiliser une seule activité de lecture pour le 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 de vidéos 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 perturber l'utilisateur.

Pour vous assurer qu'une seule activité est utilisée pour les requêtes de lecture de vidéos 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 à faible RAM. Avant que votre application n'utilise le PIP, vérifiez qu'il est disponible en appelant hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Le mode PIP est destiné aux activités de lecture de vidéos en plein écran. Lorsque vous passez votre activité en mode PIP, évitez de diffuser 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 Gérer l'interface utilisateur en mode PIP.

Lorsqu'une activité est en mode PIP, elle n'est pas sélectionnée par défaut. 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, telle qu'une application de lecteur de musique ou de recherche vocale. Pour éviter cela, demandez la sélection audio lorsque vous lancez la lecture de la vidéo et gérez les notifications de changement de mise au point audio, comme décrit dans Gérer la priorité audio. Si vous recevez une notification de perte de la priorité audio en mode PIP, mettez en pause ou arrêtez la lecture de la vidéo.

Lorsque votre application est sur le point de passer 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 maintenant 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 génère 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 est affichée et l'activité de lecture de vidéos 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 une application exemple écrite sous Android, consultez la page Exemple Picture-in-picture. Pour télécharger une application exemple écrite en Kotlin, consultez la page Exemple Android PictureInPicture (Kotlin).