Projeção de mídia

As APIs android.media.projection introduzidas no Android 5 (nível 21 da API) permitem capturar o conteúdo da tela de um dispositivo como um stream de mídia que pode ser reproduzido, gravado ou transmitido para outros dispositivos, como TVs.

O Android 14 (nível 34 da API) introduz o compartilhamento de tela do app, que permite que os usuários compartilhem uma única janela do app em vez da tela inteira do dispositivo, independente do modo de janela. O compartilhamento de tela do app exclui a barra de status, a barra de navegação, as notificações e outros elementos da interface do sistema da tela compartilhada, mesmo quando o compartilhamento de tela é usado para capturar um app em tela cheia. Apenas o conteúdo do app selecionado é compartilhado.

O compartilhamento de tela do app garante a privacidade do usuário, aumenta a produtividade do usuário e melhora a multitarefa ao permitir que os usuários executem vários apps, mas restrinja o compartilhamento de conteúdo a apenas um.

Três representações de tela

Uma projeção de mídia captura o conteúdo de uma tela de dispositivo ou janela de um app e projeta a imagem capturada em uma tela virtual que renderiza a imagem em uma Surface.

Tela do dispositivo real projetada em exibição virtual. Conteúdo da
              tela virtual gravada para a "Superfície" fornecida pelo aplicativo.
Figura 1. Tela real do dispositivo ou janela do app projetada em exibição virtual. Tela virtual gravada para a Surface fornecida pelo aplicativo.

O app fornece o Surface usando um MediaRecorder, SurfaceTexture ou ImageReader, que consome o conteúdo da tela capturada e permite gerenciar imagens renderizadas na Surface em tempo real. Salve as imagens como uma gravação ou as transmita para uma TV ou outro dispositivo.

Display real

Inicie uma sessão de projeção de mídia usando um token que conceda ao app a capacidade de capturar o conteúdo da tela do dispositivo ou da janela do app. O token é representado por uma instância da classe MediaProjection.

Use o método getMediaProjection() do serviço do sistema MediaProjectionManager para criar uma instância MediaProjection ao iniciar uma nova atividade. Inicie a atividade com uma intent do método createScreenCaptureIntent() para especificar uma operação de captura de tela:

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

Tela virtual

O centro de uma projeção de mídia é a tela virtual, que é criada chamando o método createVirtualDisplay() em uma instância 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);

Os parâmetros width e height especificam as dimensões da tela virtual. Para receber valores de largura e altura, use as APIs WindowMetrics introduzidas no Android 11 (nível 30 da API). Para saber mais, consulte a seção Tamanho da projeção de mídia.

Superfície

Dimensione a superfície da projeção de mídia para produzir a saída na resolução adequada. Torne a superfície grande (baixa resolução) para transmitir a tela para TVs ou monitores de computador e pequena (alta resolução) para gravação de telas do dispositivo.

No Android 12L (nível 32 da API) e versões mais recentes, ao renderizar o conteúdo capturado na superfície, o sistema dimensiona o conteúdo de maneira uniforme, mantendo a proporção, para que as duas dimensões do conteúdo (largura e altura) sejam iguais ou menores que as dimensões correspondentes da superfície. O conteúdo capturado é centralizado na superfície.

A abordagem de dimensionamento do Android 12L melhora a transmissão de tela para televisões e outras telas grandes maximizando o tamanho da imagem da superfície e garantindo a proporção correta.

Permissão para serviços de primeiro plano

Caso o app seja destinado ao Android 14 ou versões mais recentes, o manifesto precisa incluir uma declaração de permissão para o tipo de serviço em primeiro plano 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>

Inicie o serviço de projeção de mídia com uma chamada para startForeground().

Se o tipo de serviço em primeiro plano não for especificado na chamada, o tipo será definido por padrão como um número inteiro bit a bit dos tipos de serviço em primeiro plano definidos no manifesto. Se o manifesto não especificar nenhum tipo de serviço, o sistema vai gerar uma MissingForegroundServiceTypeException.

Seu app precisa solicitar o consentimento do usuário antes de cada sessão de projeção de mídia. Uma sessão é uma única chamada para createVirtualDisplay(). Um token MediaProjection precisa ser usado apenas uma vez para fazer a chamada.

No Android 14 ou versões mais recentes, o método createVirtualDisplay() vai gerar uma SecurityException se o app realizar uma das seguintes ações:

  • Transmite uma instância Intent retornada de createScreenCaptureIntent() para getMediaProjection() mais de uma vez
  • Chama createVirtualDisplay() mais de uma vez na mesma instância de MediaProjection

Tamanho da projeção de mídia

Uma projeção de mídia pode capturar toda a tela do dispositivo ou uma janela do app, independente do modo de janelas.

Tamanho inicial

Com a projeção de mídia em tela cheia, seu app precisa determinar o tamanho da tela do dispositivo. No compartilhamento de tela, o app não poderá determinar o tamanho da tela capturada até que o usuário selecione a região de captura. Portanto, o tamanho inicial de qualquer projeção de mídia é o tamanho da tela do dispositivo.

Use o método WindowManager getMaximumWindowMetrics() da plataforma para retornar um objeto WindowMetrics para a tela do dispositivo, mesmo que o app host de projeção de mídia esteja no modo de várias janelas, ocupando apenas parte da tela.

Para compatibilidade com o nível 14 da API e mais recentes, use o método WindowMetricsCalculator computeMaximumWindowMetrics() da biblioteca WindowManager do Jetpack.

Chame o método WindowMetrics getBounds() para conferir a largura e a altura da tela do dispositivo.

Alterações de tamanho

O tamanho da projeção de mídia pode mudar quando o dispositivo é girado ou o usuário seleciona uma janela do app como a região de captura no compartilhamento de tela do app. A projeção de mídia poderá receber efeito letterbox se o conteúdo capturado tiver um tamanho diferente das métricas máximas da janela recebidas quando a projeção de mídia foi configurada.

Para garantir que a projeção de mídia se alinhe com precisão ao tamanho do conteúdo capturado para qualquer região capturada e entre rotações do dispositivo, use o callback onCapturedContentResize() para redimensionar a captura. Para mais informações, consulte a seção Personalização, a seguir.

Personalização

Seu app pode personalizar a experiência do usuário de projeção de mídia com estas APIs de MediaProjection.Callback:

  • onCapturedContentVisibilityChanged(): ativa o app host (que iniciou a projeção de mídia) para mostrar ou ocultar o conteúdo compartilhado.

    Use esse callback para personalizar a interface do app com base na visibilidade da região capturada para o usuário. Por exemplo, se o app estiver visível para o usuário, estiver exibindo o conteúdo capturado na interface do app, e o app capturado também estiver visível para o usuário (como indicado neste callback), o usuário verá o mesmo conteúdo duas vezes. Use o callback para atualizar a interface do app para ocultar o conteúdo capturado e liberar espaço de layout no app para outros conteúdos.

  • onCapturedContentResize(): permite que o app host mude o tamanho da projeção de mídia na tela virtual e na projeção de mídia Surface com base no tamanho da região de exibição capturada.

    Acionado sempre que o conteúdo capturado, uma única janela do app ou uma tela completa do dispositivo, muda de tamanho (por causa da rotação do dispositivo ou do app capturado entrar em um modo de janela diferente). Use essa API para redimensionar a tela virtual e a superfície para garantir que a proporção corresponda ao conteúdo capturado e que a captura não tenha efeito letterbox.

Recuperação de recursos

Seu app precisa registrar o callback MediaProjection onStop() para liberar recursos retidos pelo app, como a tela virtual e a superfície de projeção.

O callback é chamado quando a projeção de mídia é encerrada ou quando o usuário não dá consentimento para continuar uma sessão de captura.

Se o app não registrar o callback e o usuário não consentir com a sessão de projeção de mídia, as chamadas para createVirtualDisplay() gerarão IllegalStateException.

Desativar

O Android 14 ou versões mais recentes ativa o compartilhamento de tela do app por padrão. Cada sessão de projeção de mídia oferece aos usuários a opção de compartilhar uma janela do app ou a tela inteira.

O app pode desativar o compartilhamento de tela chamando o método createScreenCaptureIntent(MediaProjectionConfig) com um argumento MediaProjectionConfig retornado de uma chamada para createConfigForDefaultDisplay().

Uma chamada para createScreenCaptureIntent(MediaProjectionConfig) com um argumento MediaProjectionConfig retornado de uma chamada para createConfigForUserChoice() é igual ao comportamento padrão, ou seja, uma chamada para createScreenCaptureIntent().

Apps redimensionáveis

Sempre torne seus apps de projeção de mídia redimensionáveis (resizeableActivity="true"). Os apps redimensionáveis oferecem suporte a mudanças de configuração do dispositivo e ao modo de várias janelas. Consulte Suporte a várias janelas.

Se o app não for redimensionável, ele precisará consultar os limites da tela em um contexto de janela e usar getMaximumWindowMetrics() para extrair as WindowMetrics da área máxima de exibição disponível para o 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();

Outros recursos

Para saber mais sobre projeção de mídia, consulte Capturar reproduções de vídeo e áudio.