Le Picture-in-picture (PIP) est un type spécial de mode multifenêtre principalement utilisé pour la lecture des vidéos. Elle permet à l'utilisateur de regarder une vidéo dans une petite fenêtre épinglée à situé dans un coin de l'écran pendant que vous naviguez d'une application à une autre ou lorsque vous parcourez du contenu sur la l'écran principal.
Il exploite les API multifenêtres disponibles dans Android 7.0 pour fournir la une vidéo en superposition épinglée. Pour ajouter le PIP à votre application, vous devez enregistrer votre l'activité, passez en mode PIP si nécessaire et assurez-vous que les éléments de l'UI sont masquées et la lecture de la vidéo se poursuit lorsque l'activité est en mode PIP.
Ce guide explique comment ajouter le mode PIP dans Compose à votre application à l'aide d'une vidéo Compose. la mise en œuvre. Accédez à l'appli Socialite pour découvrir les meilleures pratiques en action.
Configurer votre application pour le mode PiP
Dans la balise d'activité de votre fichier AndroidManifest.xml
, procédez comme suit:
- Ajoutez
supportsPictureInPicture
et définissez-le surtrue
pour déclarer que vous serez à l'aide du mode PIP dans votre application. Ajoutez
configChanges
et définissez-le surorientation|screenLayout|screenSize|smallestScreenSize
pour spécifier que votre activité gère les modifications de configuration de la mise en page. De cette façon, votre activité ne redémarre pas en cas de modification de la mise en page lors des transitions en mode PIP.<activity android:name=".SnippetsActivity" android:exported="true" android:supportsPictureInPicture="true" android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize" android:theme="@style/Theme.Snippets">
Dans votre code Compose, procédez comme suit :
- Ajoutez cette extension sur
Context
. Vous utiliserez cette extension plusieurs fois tout au long du guide pour accéder à l’activité.internal fun Context.findActivity(): ComponentActivity { var context = this while (context is ContextWrapper) { if (context is ComponentActivity) return context context = context.baseContext } throw IllegalStateException("Picture in picture should be called in the context of an Activity") }
Ajouter le mode PiP en quittant l'application pour les versions antérieures à Android 12
Pour ajouter la fonctionnalité PIP pour les versions antérieures à Android 12, utilisez addOnUserLeaveHintProvider
. Pour ajouter le mode PiP pour les versions antérieures à Android 12, procédez comme suit :
- Ajoutez une porte de version afin que ce code ne soit accessible que dans les versions O jusqu'à R.
- Utilisez un
DisposableEffect
avecContext
comme clé. - Dans
DisposableEffect
, définissez le comportement lorsqueonUserLeaveHintProvider
est déclenché à l'aide d'un lambda. Dans le lambda, appelezenterPictureInPictureMode()
surfindActivity()
et transmettezPictureInPictureParams.Builder().build()
. - Ajoutez
addOnUserLeaveHintListener
à l'aide defindActivity()
et transmettez le lambda. - Dans
onDispose
, ajoutezremoveOnUserLeaveHintListener
à l'aide defindActivity()
. et transmettre le lambda.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
Ajouter le mode PIP en quittant l'application pour les versions ultérieures à Android 12
Après Android 12, le PictureInPictureParams.Builder
est ajouté via un
qui est transmis au lecteur vidéo de l'application.
- Créez un
modifier
et appelezonGloballyPositioned
sur celui-ci. Les coordonnées de mise en page seront utilisées lors d'une prochaine étape. - Créez une variable pour
PictureInPictureParams.Builder()
. - Ajoutez une instruction
if
pour vérifier si le SDK est S ou supérieur. Si c'est le cas, ajoutezsetAutoEnterEnabled
au compilateur et le définir surtrue
pour entrer dans le mode PIP. lorsque vous balayez l'écran. Cela permet d'obtenir une animation plus fluideenterPictureInPictureMode
- Utilisez
findActivity()
pour appelersetPictureInPictureParams()
. Appelerbuild()
aubuilder
et le transmettre.
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(true) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Ajouter le mode PiP via un bouton
Pour passer en mode PIP en cliquant sur un bouton, appelez
enterPictureInPictureMode()
sur findActivity()
.
Ces paramètres sont déjà définis par les appels précédents à la fonction
PictureInPictureParams.Builder
. Vous n'avez donc pas besoin de définir de nouveaux paramètres.
sur le compilateur. Toutefois, si vous souhaitez modifier des paramètres
vous pouvez les définir ici.
val context = LocalContext.current Button(onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.findActivity().enterPictureInPictureMode( PictureInPictureParams.Builder().build() ) } else { Log.i(PIP_TAG, "API does not support PiP") } }) { Text(text = "Enter PiP mode!") }
Gérer votre UI en mode PIP
Lorsque vous passez en mode PIP, toute l'UI de votre application entre dans la fenêtre PIP, sauf si vous spécifier l'apparence de votre UI en mode PIP et en dehors.
Tout d'abord, vous devez savoir si votre application est en mode PiP ou non. Vous pouvez utiliser
OnPictureInPictureModeChangedProvider
.
Le code ci-dessous vous indique si votre application est en mode PIP.
@Composable fun rememberIsInPipMode(): Boolean { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val activity = LocalContext.current.findActivity() var pipMode by remember { mutableStateOf(activity.isInPictureInPictureMode) } DisposableEffect(activity) { val observer = Consumer<PictureInPictureModeChangedInfo> { info -> pipMode = info.isInPictureInPictureMode } activity.addOnPictureInPictureModeChangedListener( observer ) onDispose { activity.removeOnPictureInPictureModeChangedListener(observer) } } return pipMode } else { return false } }
Vous pouvez maintenant utiliser rememberIsInPipMode()
pour activer/désactiver les éléments d'interface utilisateur à afficher.
Lorsque l'application passe en mode PIP:
val inPipMode = rememberIsInPipMode() Column(modifier = modifier) { // This text will only show up when the app is not in PiP mode if (!inPipMode) { Text( text = "Picture in Picture", ) } VideoPlayer() }
Assurez-vous que votre application passe en mode PiP au bon moment
Votre application ne doit pas passer en mode PIP dans les situations suivantes:
- Indique si la vidéo est arrêtée ou mise en pause.
- Si vous vous trouvez sur une page de l'application différente de celle du lecteur vidéo.
Pour contrôler à quel moment votre application passe en mode PIP, ajoutez une variable qui suit l'état
du lecteur vidéo à l'aide d'un mutableStateOf
.
Activer/Désactiver l'état selon que la vidéo est en cours de lecture
Pour changer l'état selon que le lecteur vidéo est en cours de lecture, ajoutez un écouteur sur le lecteur vidéo. Activez ou désactivez l'état de votre variable d'état en fonction de la présence ou non du joueur :
player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { shouldEnterPipMode = isPlaying } })
Activer/Désactiver l'état selon que le lecteur est relâché
Lorsque le lecteur est libéré, définissez votre variable d'état sur false
:
fun releasePlayer() { shouldEnterPipMode = false }
Utiliser l'état pour définir si le mode PiP est activé (avant Android 12)
- Étant donné que l'ajout du PIP antérieur à la version 12 utilise un
DisposableEffect
, vous devez créer une nouvelle variable derememberUpdatedState
en définissantnewValue
comme variable d'état. Vous aurez ainsi l'assurance que la version mise à jour sera utilisée dans leDisposableEffect
Dans le lambda qui définit le comportement lorsque
OnUserLeaveHintListener
est déclenché, ajoutez une instructionif
avec la variable d'état autour de l'appel deenterPictureInPictureMode()
:val currentShouldEnterPipMode by rememberUpdatedState(newValue = shouldEnterPipMode) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && Build.VERSION.SDK_INT < Build.VERSION_CODES.S ) { val context = LocalContext.current DisposableEffect(context) { val onUserLeaveBehavior: () -> Unit = { if (currentShouldEnterPipMode) { context.findActivity() .enterPictureInPictureMode(PictureInPictureParams.Builder().build()) } } context.findActivity().addOnUserLeaveHintListener( onUserLeaveBehavior ) onDispose { context.findActivity().removeOnUserLeaveHintListener( onUserLeaveBehavior ) } } } else { Log.i("PiP info", "API does not support PiP") }
Utiliser l'état pour déterminer si le mode PIP est activé (postérieur à Android 12)
Transmettez votre variable d'état à setAutoEnterEnabled
afin que votre application n'entre que
le mode PIP au bon moment:
val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() // Add autoEnterEnabled for versions S and up if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Utiliser setSourceRectHint
pour implémenter une animation fluide
L'API setSourceRectHint
crée une animation plus fluide pour passer en mode PiP. Dans Android 12 et versions ultérieures, cette fonctionnalité crée également une animation plus fluide pour quitter le mode PIP.
Ajoutez cette API au compilateur PIP pour indiquer la zone de l'activité qui est
visible après la transition vers le mode PIP.
- N'ajoutez
setSourceRectHint()
àbuilder
que si l'état définit que le l'application devrait passer en mode PIP. Cela évite de calculersourceRect
lorsque l'application n'a pas besoin de passer en mode PIP. - Pour définir la valeur
sourceRect
, utilisez leslayoutCoordinates
fournis. à partir de la fonctiononGloballyPositioned
du modificateur. - Appelez
setSourceRectHint()
surbuilder
et transmettezsourceRect
. .
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Utiliser setAspectRatio
pour définir le format de la fenêtre PIP
Pour définir le format de la fenêtre PIP, vous pouvez choisir un format spécifique
ou utiliser la largeur et la hauteur de la taille de la vidéo du lecteur. Si vous utilisez
à l'aide d'un lecteur media3, vérifiez que la valeur du lecteur n'est pas nulle et que la valeur
la taille de la vidéo n'est pas égale à VideoSize.UNKNOWN
avant de définir l'aspect
ratio.
val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(pipModifier)
Si vous utilisez un lecteur personnalisé, définissez le format sur la hauteur du lecteur et sa largeur à l'aide de la syntaxe propre à votre lecteur. Sachez que si votre lecteur est redimensionnée lors de l'initialisation, s'il n'est pas compris quel que soit le format, votre application plantera. Vous devrez peut-être ajouter des vérifications pour savoir quand le format peut être calculé, comme c'est le cas pour un lecteur media3.
Ajouter des actions à distance
Si vous souhaitez ajouter des commandes (lecture, pause, etc.) à votre fenêtre PIP, créez une
RemoteAction
pour chaque commande à ajouter.
- Ajoutez des constantes à vos commandes de diffusion:
// Constant for broadcast receiver const val ACTION_BROADCAST_CONTROL = "broadcast_control" // Intent extras for broadcast controls from Picture-in-Picture mode. const val EXTRA_CONTROL_TYPE = "control_type" const val EXTRA_CONTROL_PLAY = 1 const val EXTRA_CONTROL_PAUSE = 2
- Créez une liste de
RemoteActions
pour les commandes de votre fenêtre PIP. - Ajoutez ensuite un
BroadcastReceiver
et remplacezonReceive()
pour définir les actions de chaque bouton. Utilisez unDisposableEffect
pour enregistrer le récepteur et les actions à distance. Une fois le joueur éliminé, annulez l'enregistrement destinataire.@RequiresApi(Build.VERSION_CODES.O) @Composable fun PlayerBroadcastReceiver(player: Player?) { val isInPipMode = rememberIsInPipMode() if (!isInPipMode || player == null) { // Broadcast receiver is only used if app is in PiP mode and player is non null return } val context = LocalContext.current DisposableEffect(player) { val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if ((intent == null) || (intent.action != ACTION_BROADCAST_CONTROL)) { return } when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) { EXTRA_CONTROL_PAUSE -> player.pause() EXTRA_CONTROL_PLAY -> player.play() } } } ContextCompat.registerReceiver( context, broadcastReceiver, IntentFilter(ACTION_BROADCAST_CONTROL), ContextCompat.RECEIVER_NOT_EXPORTED ) onDispose { context.unregisterReceiver(broadcastReceiver) } } }
- Transmettez une liste de vos actions à distance à
PictureInPictureParams.Builder
:val context = LocalContext.current val pipModifier = modifier.onGloballyPositioned { layoutCoordinates -> val builder = PictureInPictureParams.Builder() builder.setActions( listOfRemoteActions() ) if (shouldEnterPipMode && player != null && player.videoSize != VideoSize.UNKNOWN) { val sourceRect = layoutCoordinates.boundsInWindow().toAndroidRectF().toRect() builder.setSourceRectHint(sourceRect) builder.setAspectRatio( Rational(player.videoSize.width, player.videoSize.height) ) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { builder.setAutoEnterEnabled(shouldEnterPipMode) } context.findActivity().setPictureInPictureParams(builder.build()) } VideoPlayer(modifier = pipModifier)
Étapes suivantes
Dans ce guide, vous avez découvert les bonnes pratiques à suivre pour ajouter le mode PiP dans Compose avant et après Android 12.
- Accédez à l'application Socialite pour connaître les bonnes pratiques concernant les Le mode PIP de Compose en action.
- Pour en savoir plus, consultez les conseils de conception des adresses IPP.