Cómo agregar videos con el modo pantalla en pantalla (PIP)

A partir de Android 8.0 (nivel de API 26), Android permite que las actividades se inicien en el modo de pantalla en pantalla (PIP). PIP es un tipo especial de modo multiventana que se usa principalmente para la reproducción de videos. Permite al usuario ver un video en una ventana pequeña fijada en una esquina de la pantalla mientras navega entre apps o explora contenido en la pantalla principal.

PIP aprovecha las API de multiventana disponibles en Android 7.0 para proporcionar la ventana de video fijada superpuesta. Para agregar PIP a tu app, debes registrar las actividades que admitan PIP, cambiar tu actividad al modo de PIP según sea necesario y asegurarte de que los elementos de la IU estén ocultos y la reproducción de video continúe cuando la actividad esté en modo de PIP.

La ventana de PIP aparece en la capa superior de la pantalla, en una esquina elegida por el sistema.

También se admite PIP en dispositivos con el SO Android TV compatibles que ejecutan Android 14 (nivel de API 34) o una versión posterior Si bien hay muchas similitudes, hay consideraciones adicionales cuando se usan PIP en TV:

Cómo pueden interactuar los usuarios con la ventana de PIP

Los usuarios pueden arrastrar la ventana de PIP a otra ubicación. A partir de Android 12, los usuarios también pueden hacer lo siguiente:

  • Presionar una vez la ventana para mostrar un botón de activación de pantalla completa, un botón de cierre, un botón de configuración y acciones personalizadas que proporciona tu app (por ejemplo, controles de reproducción).

  • Presiona dos veces la ventana para alternar entre el tamaño actual de PIP y el máximo. o tamaño mínimo de PIP, por ejemplo, presionar dos veces una ventana maximizada lo minimiza y lo contrario también es cierto.

  • Para guardar la ventana, arrástrala hacia el borde izquierdo o derecho. Para dejar de almacenar las presiona la parte visible de la ventana almacenada o arrástrala hacia afuera.

  • Cambiar el tamaño de la ventana de PIP con la función de pellizcar para hacerle zoom.

Tu app controla cuándo ingresa en modo de PIP la actividad actual. Estos son algunos ejemplos:

  • Una actividad puede ingresar al modo de PIP cuando el usuario presiona el botón de inicio o desliza el dedo hasta casa. Así es como Google Maps sigue mostrando indicaciones mientras el usuario ejecuta otra actividad al mismo tiempo.

  • Tu app puede mover un video al modo de PIP cuando el usuario navega hacia atrás desde el video para explorar otro contenido.

  • Tu app puede cambiar un video al modo de PIP mientras un usuario mira el final de un episodio de contenido. La pantalla principal muestra información promocional o de resumen sobre el próximo episodio de la serie.

  • Tu app puede proporcionar una manera para que los usuarios pongan en cola contenido adicional mientras miran un video. El video continúa reproduciéndose en modo de PIP mientras se reproduce La pantalla muestra una actividad de selección de contenido.

Cómo declarar la compatibilidad con PIP

De forma predeterminada, el sistema no admite automáticamente PIP para apps. Si quieres admitir PIP en tu app, registra tu actividad de video en tu manifiesto estableciendo android:supportsPictureInPicture en true. Además, especifica que tu la actividad maneja los cambios de configuración de diseño para que tu actividad no se reiniciarán cuando se produzcan cambios en el diseño durante las transiciones del modo de PIP.

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

Cómo cambiar tu actividad a PIP

A partir de Android 12, puedes cambiar tu actividad al modo de PIP estableciendo la marca setAutoEnterEnabled en true. Con este parámetro de configuración, una actividad cambia automáticamente al modo de PIP según sea necesario sin tener que llamar explícitamente enterPictureInPictureMode() en onUserLeaveHint. Y aquí tenemos una ventaja adicional de brindar transiciones mucho más fluidas. Para obtener más información, consulta Make las transiciones al modo de PIP más fluidas desde la navegación por gestos

Si la orientas a Android 11 o versiones anteriores, una actividad debe llamar enterPictureInPictureMode() para cambiar al modo de PIP. Por ejemplo, el siguiente código cambia una actividad a Modo de PIP cuando el usuario hace clic en un botón dedicado en la IU de la app:

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;
    }
    ...
}

Te recomendamos que incluyas lógica que cambie una actividad al modo de PIP en su lugar de pasar a segundo plano. Por ejemplo, Google Maps cambia al modo de PIP si el usuario presiona el botón Recientes o de inicio mientras la app está navegando. Para solucionar este caso, anula onUserLeaveHint():

Kotlin

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

Java

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

Recomendado: Proporciona a los usuarios una experiencia de transición de PIP mejorada

En Android 12, se agregaron mejoras estéticas significativas a las transiciones animadas entre las ventanas de pantalla completa y de PIP. Te recomendamos implementar todas cambios aplicables; una vez que lo hayas hecho, estos cambios se ajustarán automáticamente pantallas grandes, como plegables y tablets, sin ningún trabajo adicional requerido.

Si tu app no incluye actualizaciones correspondientes, las transiciones de PIP se mantienen funcionales, pero las animaciones están menos pulidas. Por ejemplo, la transición de pantalla completa al modo de PIP puede hacer que la ventana de PIP desaparezca durante la una transición antes de que vuelva a aparecer cuando se complete la transición.

Estos cambios implican lo siguiente:

  • Cómo hacer que las transiciones al modo de PIP sean más fluidas desde la navegación por gestos
  • Configura un sourceRectHint adecuado para entrar al modo de PIP y salir de él
  • Cómo inhabilitar el cambio fluido de tamaño de contenido que no sea de video

Consulta el artículo Android Ejemplo de PictureInPicture de Kotlin como referencia para permitir una experiencia de transición pulida.

Cómo lograr que las transiciones al modo de PIP sean más fluidas desde la navegación por gestos

A partir de Android 12, la marca setAutoEnterEnabled ofrece mucho animación más fluida para la transición a contenido de video en modo de PIP con gestos navegación, por ejemplo, al deslizar el dedo hacia arriba desde la pantalla principal para ir a la pantalla principal.

Completa los siguientes pasos para realizar este cambio y consulta este ejemplo para ver un referencia:

  1. Usa setAutoEnterEnabled para construir 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. Llama a setPictureInPictureParams con la clase PictureInPictureParams actualizada con anticipación. La app no espera onUserLeaveHint (como se habría hecho en Android 11).

    Por ejemplo, es posible que quieras llamar a setPictureInPictureParams en el primera reproducción y cualquier reproducción siguiente si se cambia la relación de aspecto.

  3. Llama a setAutoEnterEnabled(false), pero solo cuando sea necesario. Por ejemplo: probablemente no quieras ingresar al modo de PIP si la reproducción actual está en pausa para cada estado.

Establece un sourceRectHint adecuado para entrar al modo de PIP y salir de él.

Comenzando con la introducción de PIP en Android 8.0, setSourceRectHint indicó el área de la actividad visible después de la transición a pantalla en pantalla (por ejemplo, los límites de la vista de video en un reproductor de video)

Con Android 12, el sistema usa sourceRectHint para implementar una versión mucho más fluida animación cuando entra y sale del modo de PIP.

Si quieres configurar correctamente sourceRectHint para ingresar al modo de PIP y salir de él, haz lo siguiente:

  1. Construir PictureInPictureParams con los límites adecuados como sourceRectHint. Te recomendamos que también adjuntes un objeto de escucha de cambio de diseño al reproductor de video:

    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 es necesario, actualiza sourceRectHint antes de que el sistema inicie la de salida. Cuando el sistema está a punto de salir del modo de PIP, el estado de la actividad la jerarquía de vistas se establece en su configuración de destino (por ejemplo, pantalla completa). La app puede adjuntar un objeto de escucha de cambios de diseño a su vista raíz. o de destino (como la vista del reproductor de video) para detectar el evento y actualiza el objeto sourceRectHint antes de que comience la animación.

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

Cómo inhabilitar el cambio fluido de tamaño de contenido que no sea video

En Android 12, se agrega la marca setSeamlessResizeEnabled, que proporciona Animación entre difuminados más fluida cuando se cambia el tamaño de contenido que no es video en PIP en la ventana modal. Antes, si se cambiaba el tamaño de contenido que no era video en una ventana de PIP, artefactos visuales molestos.

Para inhabilitar el cambio fluido de tamaño de contenido que no sea video, usa el siguiente fragmento de código:

Kotlin

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

Java

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

Cómo controlar la IU durante PIP

Cuando la actividad ingresa al modo de PIP o sale de este, el sistema llama Activity.onPictureInPictureModeChanged() o Fragment.onPictureInPictureModeChanged()

Debes anular estas devoluciones de llamada para volver a dibujar los elementos de la IU de la actividad. Ten en cuenta que, en el modo de PIP, tu actividad se muestra en una ventana pequeña. Los usuarios no pueden interactuar con los elementos de la IU de la app cuando están en modo de PIP, y los detalles de los elementos pequeños de la IU pueden ser difíciles de ver. Las actividades de reproducción de video con una IU mínima proporcionan la mejor experiencia del usuario.

Si tu app necesita proporcionar acciones personalizadas para PIP, consulta Cómo agregar controles en esta página. Quita otros elementos de la IU antes de que la actividad ingrese a PIP y restablécelos cuando vuelva a ser de pantalla completa:

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.
        ...
    }
}

Agregar controles

La ventana de PIP puede mostrar controles cuando el usuario abre el menú de la ventana (tocando la ventana en un dispositivo móvil o seleccionando el menú desde el control remoto de la TV).

Si una app tiene un contenido multimedia activo abierta y, luego, reproducir, pausa, Siguiente y Anterior.

También puedes especificar acciones personalizadas explícitamente compilando PictureInPictureParams con PictureInPictureParams.Builder.setActions() antes de ingresar al modo de PIP y pasa los parámetros cuando ingresas enterPictureInPictureMode(android.app.PictureInPictureParams) o setPictureInPictureParams(android.app.PictureInPictureParams). Cuidado. Si intentas agregar más de getMaxNumPictureInPictureActions(), solo obtendrás el número máximo.

Cómo continuar con la reproducción de video durante la reproducción en PIP

Cuando tu actividad cambia a PIP, el sistema la ubica en el modo y llama al estado de la actividad onPause(). Video: la reproducción no se debe pausar y, en su lugar, continúa la reproducción si la actividad se pausa durante la transición al modo de PIP.

En Android 7.0 y versiones posteriores, debes pausar y reanudar la reproducción de video cuando el sistema llame a los elementos onStop() y onStart() de tu actividad. Al hacer esto, Puedes evitar tener que verificar si tu app está en modo de PIP en onPause() y continuar explícitamente la reproducción.

Si no estableciste la marca setAutoEnterEnabled en true y necesitas pausa la reproducción en tu implementación de onPause(), verifica el modo de PIP llamando isInPictureInPictureMode() y controlar la reproducción de manera adecuada. Por ejemplo:

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.
        ...
    }
}

Cuando tu actividad vuelve al modo de pantalla completa, el sistema reanuda tu actividad y llama a tu onResume().

Cómo usar una sola actividad de reproducción para PIP

En tu app, un usuario puede seleccionar un video nuevo cuando busca contenido en la pantalla principal, mientras una actividad de reproducción de video está en modo de PIP. Reproduce el video nuevo en la actividad de reproducción existente en el modo de pantalla completa, en lugar de iniciar actividad nueva que podría confundir al usuario.

Para garantizar que se use una sola actividad para las solicitudes de reproducción de video y se cambien para activar o desactivar el modo de PIP según sea necesario, establece el android:launchMode de la actividad en singleTask en tu manifiesto:

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

En la actividad, anula onNewIntent() y controla el video nuevo, lo cual detiene cualquier reproducción de video existente si es necesario.

Recomendaciones

El modo de PIP puede estar inhabilitado en dispositivos que tienen poca RAM. Antes de que tu app use PIP, verifica que esté disponible llamando a hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

El modo de PIP está destinado a actividades que reproducen video en pantalla completa. Cuando cambies tu actividad al modo de PIP, evita mostrar algo que no sea contenido de video. Haz un seguimiento de cuándo Tu actividad ingresa al modo de PIP y oculta los elementos de la IU, como se describe en Cómo controlar la IU. durante PIP.

Cuando una actividad está en modo de PIP, de forma predeterminada, no recibe el foco de entrada. Para recibir eventos de entrada mientras se está usando el modo de PIP, usa MediaSession.setCallback(). Para obtener más información sobre el uso de setCallback(), consulta Cómo mostrar un mensaje de "Está sonando" personalizada.

Cuando tu app está en modo de PIP, la reproducción de video en la ventana de PIP puede provocar audio. interferencia con otra app, como una app de reproducción de música o una app de búsqueda por voz. Para evitar esto, solicita el foco de audio cuando comiences a reproducir el video y controla las notificaciones de cambio de foco de audio, como se describe en Cómo administrar audio Enfoque. Si recibes una notificación de pérdida de foco de audio cuando se utiliza el modo de PIP, pausa o detén la reproducción de video.

Cuando tu app esté a punto de ingresar a PIP, ten en cuenta que solo ingresa la actividad principal pantalla en pantalla. En algunos casos, como en los dispositivos multiventana, es posible que la actividad menor se muestre y se vuelva a ver junto con la actividad en modo de PIP. Debes controlar esta situación como corresponde, lo cual incluye proporcionar una devolución de llamada onResume() o onPause() a la actividad menor. También es posible que el usuario interactúe con la actividad. Por ejemplo, si se mostrara una actividad de lista de videos y la actividad de reproducción de video estuviera en modo de PIP, el usuario podría seleccionar un video nuevo de la lista y la actividad en modo de PIP debería actualizarse en consecuencia.

Código de ejemplo adicional

Para descargar una app de ejemplo escrita en Kotlin, consulta Muestra de PictureInPicture de Android (Kotlin).