Proyección de contenido multimedia

Las APIs de android.media.projection que se introdujeron en Android 5 (nivel de API 21) te permiten capturar el contenido de la pantalla de un dispositivo como una transmisión de contenido multimedia que puedes reproducir, grabar o transmitir a otros dispositivos, como TVs.

Android 14 (nivel de API 34) presenta la opción de compartir pantalla de apps, que permite a los usuarios compartir una sola ventana de la app en lugar de toda la pantalla del dispositivo, independientemente del modo de renderización en ventanas. La función para compartir pantalla de la app excluye de la pantalla compartida la barra de estado, la barra de navegación, las notificaciones y otros elementos de la IU del sistema, incluso cuando se usa esta función para capturar una app en pantalla completa. Solo se comparte el contenido de la app seleccionada.

La función para compartir pantalla en las apps garantiza la privacidad del usuario, aumenta la productividad de estos y mejora la capacidad de realizar varias tareas a la vez, ya que permite que los usuarios ejecuten varias apps, pero restringe el uso compartido de contenido a una sola app.

Tres representaciones de pantalla

Una proyección de contenido multimedia captura el contenido de la pantalla de un dispositivo o de la ventana de una app y, luego, proyecta la imagen capturada en una pantalla virtual que renderiza la imagen en un Surface.

Pantalla real de un dispositivo proyectada en una pantalla virtual. Contenido de la pantalla virtual escrito en "Surface" que brinda la aplicación.
Figura 1: Pantalla real del dispositivo o ventana de la app proyectada en una pantalla virtual. Pantalla virtual escrita en Surface proporcionado por la aplicación.

La aplicación proporciona el Surface mediante un MediaRecorder, SurfaceTexture o ImageReader, que consume el contenido de la pantalla capturada y te permite administrar las imágenes que se renderizan en Surface en tiempo real. Puedes guardar las imágenes como una grabación o transmitirlas a una TV o a otro dispositivo.

Pantalla real

Para iniciar una sesión de proyección de contenido multimedia, obtén un token que le otorgue a tu app la capacidad de capturar el contenido de la pantalla del dispositivo o de la ventana de la app. Una instancia de la clase MediaProjection representa el token.

Usa el método getMediaProjection() del servicio del sistema MediaProjectionManager para crear una instancia de MediaProjection cuando inicies una actividad nueva. Inicia la actividad con un intent del método createScreenCaptureIntent() para especificar una operación de captura de pantalla:

Kotlin

val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection

val startMediaProjection = registerForActivityResult(
    StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        mediaProjection = mediaProjectionManager
            .getMediaProjection(result.resultCode, result.data!!)
    }
}

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())

Java

final MediaProjectionManager mediaProjectionManager =
    getSystemService(MediaProjectionManager.class);
final MediaProjection[] mediaProjection = new MediaProjection[1];

ActivityResultLauncher<Intent> startMediaProjection = registerForActivityResult(
    new StartActivityForResult(),
    result -> {
        if (result.getResultCode() == Activity.RESULT_OK) {
            mediaProjection[0] = mediaProjectionManager
                .getMediaProjection(result.getResultCode(), result.getData());
        }
    }
);

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

Pantalla virtual

La pieza central de una proyección de contenido multimedia es la pantalla virtual, que creas cuando llamas a createVirtualDisplay() en una instancia de MediaProjection:

Kotlin

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

Java

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null);

Los parámetros width y height especifican las dimensiones de la pantalla virtual. Para obtener valores de ancho y alto, usa las APIs de WindowMetrics que se introdujeron en Android 11 (nivel de API 30). (Para obtener más información, consulta la sección Tamaño de proyección de contenido multimedia).

Superficie

Ajusta el tamaño de la superficie de proyección de contenido multimedia para producir resultados con la resolución adecuada. Haz que la superficie sea grande (baja resolución) para transmitir la pantalla a TVs o monitores de computadora y una pequeña (alta resolución) para grabar la pantalla del dispositivo.

A partir de Android 12L (nivel de API 32), cuando se renderiza contenido capturado en la superficie, el sistema escala el contenido de manera uniforme y mantiene la relación de aspecto, de modo que ambas dimensiones del contenido (ancho y altura) sean iguales o inferiores a las dimensiones correspondientes de la superficie. Luego, el contenido capturado se centra en la superficie.

El enfoque de escalamiento de Android 12L mejora la transmisión de pantalla a televisiones y otras pantallas grandes, ya que maximiza el tamaño de la imagen de la superficie, a la vez que garantiza la relación de aspecto adecuada.

Permiso de servicios en primer plano

Si la app se orienta a Android 14 o versiones posteriores, el manifiesto de la app debe incluir una declaración de permisos para el tipo de servicio en primer plano de mediaProjection:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

Inicia el servicio de proyección de contenido multimedia con una llamada a startForeground().

Si no especificas el tipo de servicio en primer plano en la llamada, el tipo se establece de forma predeterminada en un número entero a nivel de bits de los tipos de servicio en primer plano definidos en el manifiesto. Si el manifiesto no especifica ningún tipo de servicio, el sistema arroja una MissingForegroundServiceTypeException.

Tu app debe solicitar el consentimiento del usuario antes de cada sesión de proyección de contenido multimedia. Una sesión es una llamada única a createVirtualDisplay(). Un token MediaProjection debe usarse solo una vez para hacer la llamada.

En Android 14 o versiones posteriores, el método createVirtualDisplay() arroja una SecurityException si tu app realiza alguna de las siguientes acciones:

  • Pasa una instancia de Intent que se muestra de createScreenCaptureIntent() a getMediaProjection() más de una vez
  • Llama a createVirtualDisplay() más de una vez en la misma instancia de MediaProjection

Tamaño de proyección de contenido multimedia

Una proyección de contenido multimedia puede capturar toda la pantalla del dispositivo o la ventana de una app, independientemente del modo de renderización en ventanas.

Tamaño inicial

Con la proyección de contenido multimedia de pantalla completa, tu app debe determinar el tamaño de la pantalla del dispositivo. En la función de compartir pantalla, la app no podrá determinar el tamaño de la pantalla capturada hasta que el usuario seleccione la región de captura. Por lo tanto, el tamaño inicial de cualquier proyección de contenido multimedia es el tamaño de la pantalla del dispositivo.

Usa el método WindowManager getMaximumWindowMetrics() de la plataforma para mostrar un objeto WindowMetrics para la pantalla del dispositivo, incluso si la app host de proyección de contenido multimedia está en modo multiventana y ocupa solo una parte de la pantalla.

Para obtener compatibilidad hasta el nivel de API 14, usa el método WindowMetricsCalculator computeMaximumWindowMetrics() de la biblioteca WindowManager de Jetpack.

Llama al método WindowMetrics getBounds() para obtener el ancho y la altura de la pantalla del dispositivo.

Cambios de tamaño

El tamaño de la proyección de contenido multimedia puede cambiar cuando se rota el dispositivo o el usuario selecciona una ventana de la app como la región de captura en la pantalla compartida de la app. La proyección de contenido multimedia puede tener formato letterbox si el contenido capturado tiene un tamaño diferente a las métricas máximas de la ventana obtenidas cuando se configuró esa proyección de contenido multimedia.

Para garantizar que la proyección de contenido multimedia se alinee con precisión con el tamaño del contenido capturado para cualquier región capturada y entre las rotaciones del dispositivo, usa la devolución de llamada onCapturedContentResize() para cambiar el tamaño de la captura. (Para obtener más información, consulta la sección Personalización, que se encuentra a continuación).

Personalización

Tu app puede personalizar la experiencia del usuario de proyección de contenido multimedia con las siguientes APIs de MediaProjection.Callback:

  • onCapturedContentVisibilityChanged(): Permite que la app host (la que inició la proyección de contenido multimedia) muestre u oculte el contenido compartido.

    Usa esta devolución de llamada para personalizar la IU de tu app en función de si la región capturada es visible para el usuario. Por ejemplo, si tu app es visible para el usuario y muestra el contenido capturado dentro de su IU, y la app capturada también es visible para el usuario (como se indica a través de esta devolución de llamada), el usuario ve el mismo contenido dos veces. Usa la devolución de llamada para actualizar la IU de tu app a fin de ocultar el contenido capturado y liberar espacio de diseño en la app para otro contenido.

  • onCapturedContentResize(): Permite que la app host cambie el tamaño de la proyección de contenido multimedia en la pantalla virtual y la proyección de contenido multimedia Surface en función del tamaño de la región de la pantalla capturada.

    Se activa cada vez que el contenido capturado (una sola ventana de la app o una pantalla completa del dispositivo) cambia de tamaño (debido a la rotación del dispositivo o a que la app capturada ingresa a un modo de renderización en ventanas diferente). Usa esta API para cambiar el tamaño de la pantalla y la superficie virtual a fin de asegurarte de que la relación de aspecto coincida con el contenido capturado y que la captura no esté en formato letterbox.

Recuperación de recursos

Tu app debe registrar la devolución de llamada MediaProjection onStop() para liberar los recursos que retiene, como la pantalla virtual y la superficie de proyección.

Se llama a la devolución de llamada cuando finaliza la proyección de contenido multimedia o cuando el usuario no otorga su consentimiento para continuar una sesión de captura.

Si tu app no registra la devolución de llamada y el usuario no da su consentimiento para la sesión de proyección de contenido multimedia, las llamadas a createVirtualDisplay() arrojan IllegalStateException.

Inhabilitar

Android 14 o las versiones posteriores habilitan la opción para compartir pantalla de las apps de forma predeterminada. Cada sesión de proyección de contenido multimedia ofrece a los usuarios la opción de compartir la ventana de una app o toda la pantalla.

Tu app puede inhabilitar la opción para compartir pantalla mediante una llamada al método createScreenCaptureIntent(MediaProjectionConfig) con un argumento MediaProjectionConfig que se muestra a partir de una llamada a createConfigForDefaultDisplay().

Una llamada a createScreenCaptureIntent(MediaProjectionConfig) con un argumento MediaProjectionConfig que muestra una llamada a createConfigForUserChoice() es igual que el comportamiento predeterminado, es decir, una llamada a createScreenCaptureIntent().

Apps que pueden cambiar de tamaño

Siempre haz que tus apps de proyección de contenido multimedia puedan cambiar de tamaño (resizeableActivity="true"). Las apps que pueden cambiar de tamaño admiten cambios en la configuración del dispositivo y el modo multiventana (consulta Compatibilidad con el modo multiventana).

Si no se puede cambiar el tamaño de tu app, esta debe consultar los límites de la pantalla desde un contexto de ventana y usar getMaximumWindowMetrics() para recuperar el objeto WindowMetrics del área de visualización máxima disponible para la app :

Kotlin

val windowContext = context.createWindowContext(context.display!!,
      WindowManager.LayoutParams.TYPE_APPLICATION, null)
val projectionMetrics = windowContext.getSystemService(WindowManager::class.java)
      .maximumWindowMetrics

Java

Context windowContext = context.createWindowContext(context.getDisplay(),
      WindowManager.LayoutParams.TYPE_APPLICATION, null);
WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class)
      .getMaximumWindowMetrics();

Recursos adicionales

Para obtener más información sobre la proyección de contenido multimedia, consulta Cómo capturar la reproducción de audio y video.