Usar picture-in-picture (PiP)

Experimente trabalhar com o Compose
O Jetpack Compose é o kit de ferramentas de IU recomendado para Android. Saiba como oferecer suporte ao picture-in-picture no Compose.

A partir do Android 8.0 (API de nível 26), o Android permite que as atividades sejam iniciadas no modo picture-in-picture. O PiP é um tipo especial de modo de várias janelas usado para reprodução de vídeo, videochamadas e navegação. Ele permite que o usuário fixe a janela de atividade atual em um canto da tela enquanto navega entre apps ou pelo conteúdo na tela principal.

O modo picture-in-picture aproveita as APIs de várias janelas disponíveis no Android 7.0 para fornecer a janela fixa de sobreposição de vídeo. Para adicionar o picture-in-picture ao seu app, você precisa registrar suas atividades compatíveis com esse modo, alternar sua atividade para ele conforme necessário e verificar se os elementos da IU estão ocultos e se a reprodução continua quando a atividade está no modo picture-in-picture.

A janela PIP aparece na camada superior da tela, em um canto escolhido pelo sistema.

O PiP também é compatível com dispositivos Android TV OS com o Android 14 (nível 34 da API) ou mais recente. Embora haja muitas semelhanças, há outras considerações ao usar o PiP na TV.

Como os usuários podem interagir com a janela picture-in-picture

É possível arrastar a janela de picture-in-picture para outro local. A partir do Android 12, os usuários também podem fazer o seguinte:

  • Tocar na janela para exibir um botão de alternância de tela cheia, um botão "Fechar", um botão de configurações e ações personalizadas fornecidas pelo seu app, por exemplo, controles de reprodução.

  • Toque duas vezes na janela para alternar entre o tamanho atual do PiP e o tamanho máximo ou mínimo. Por exemplo, tocar duas vezes em uma janela maximizada a minimiza, e o inverso também é verdadeiro.

  • Oculte a janela arrastando-a para a borda esquerda ou direita. Para reexibir a janela, toque na parte visível dela ou arraste-a para fora.

  • Redimensionar a janela de picture-in-picture usando o gesto de pinça para aplicar zoom.

Seu app controla quando a atividade atual entra no modo picture-in-picture. Veja alguns exemplos:

  • Uma atividade pode entrar no modo picture-in-picture quando o usuário toca no botão home ou desliza para cima. É assim que o Google Maps continua exibindo rotas enquanto o usuário executa outra atividade ao mesmo tempo.

  • O app pode mover um vídeo para o modo picture-in-picture quando o usuário volta do vídeo para navegar por outro conteúdo.

  • O app pode alternar um vídeo para o modo picture-in-picture enquanto um usuário assiste o final de um episódio de conteúdo. A tela principal exibe informações promocionais ou resumidas sobre o próximo episódio da série.

  • Seu app pode oferecer uma forma de os usuários colocarem outros conteúdos em fila enquanto assistem um vídeo. O vídeo continua sendo reproduzido no modo PIP enquanto a tela principal mostra uma atividade de seleção de conteúdo.

Declarar suporte ao PiP

Por padrão, o sistema não é automaticamente compatível com o picture-in-picture para apps. Se você quiser oferecer suporte ao PiP no seu app, registre a atividade de vídeo no manifesto definindo android:supportsPictureInPicture como true. Além disso, especifique que a atividade processa as mudanças de configuração de layout para que ela não seja reiniciada quando ocorrerem mudanças de layout durante as transições do modo PIP.

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

Implementar o PiP com o Jetpack

Use a biblioteca picture-in-picture do Jetpack para implementar a experiência picture-in-picture, já que ela simplifica a integração e reduz problemas comuns no app. Consulte nosso app de exemplo da plataforma para ver um exemplo de uso. No entanto, se você preferir implementar o PiP usando as APIs da plataforma, consulte a documentação a seguir.

Mudar sua atividade para PiP

No Android 12 e versões mais recentes, é possível mudar a atividade para o modo PIP definindo a flag setAutoEnterEnabled como true. Com essa configuração, uma atividade muda automaticamente para o modo PIP, conforme necessário, sem precisar chamar explicitamente enterPictureInPictureMode() em onUserLeaveHint. Isso tem o benefício adicional de fornecer transições muito mais suaves. Para mais detalhes, consulte Suavizar as transições para o modo PIP na navegação por gestos.

Se você estiver segmentando o Android 11 ou versões anteriores, uma atividade precisará chamar enterPictureInPictureMode() para alternar para o modo PIP. Por exemplo, o código abaixo alterna uma atividade para o modo PIP quando o usuário clica em um botão dedicado na interface do app:

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Talvez você queira incluir uma lógica que mude uma atividade para o modo PIP em vez de ir para o segundo plano. Por exemplo, o Google Maps alternará para o modo picture-in-picture se o usuário pressionar o botão "Início" ou "Recentes" enquanto o app estiver navegando. É possível capturar esse caso modificando onUserLeaveHint():

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

Recomendado: oferecer aos usuários uma experiência de transição de PiP refinada

O Android 12 adicionou melhorias estéticas significativas às transições animadas entre as janelas de tela cheia e PiP. Recomendamos implementar todas as mudanças aplicáveis. Depois disso, elas serão dimensionadas automaticamente para telas grandes, como dobráveis e tablets, sem que você precise fazer mais nada.

Se o app não incluir as atualizações aplicáveis, as transições de PIP ainda vão funcionar, mas as animações serão menos refinadas. Por exemplo, a transição do modo de tela cheia para o PiP pode fazer com que a janela do PiP desapareça durante a transição antes de reaparecer quando ela for concluída.

Essas mudanças envolvem o seguinte:

  • Transições mais suaves para o modo PIP na navegação por gestos
  • Definir um sourceRectHint adequado para entrar e sair do modo PIP
  • Desativar o redimensionamento contínuo para conteúdo que não é de vídeo

Consulte o exemplo de PictureInPicture do Android Kotlin como referência para ativar uma experiência de transição refinada.

Suavizar transições no modo picture-in-picture pela navegação por gestos

No Android 12, a flag setAutoEnterEnabled oferece uma animação muito mais suave para a transição para o conteúdo de vídeo no modo picture-in-picture usando a navegação por gestos. Por exemplo, ao deslizar para cima para voltar à tela inicial no modo de tela cheia.

Siga estas etapas para fazer essa mudança:

  1. Use setAutoEnterEnabled para criar PictureInPictureParams.Builder:
setPictureInPictureParams(PictureInPictureParams.Builder()
  .setAspectRatio(aspectRatio)
  .setSourceRectHint(sourceRectHint)
  .setAutoEnterEnabled(true)
  .build())
Note: When `setAutoEnterEnabled` is enabled, you don't need to explicitly
call [`enterPictureInPictureMode`][5] in [`onUserLeaveHint`][6].
  1. Chame setPictureInPictureParams usando os PictureInPictureParams atualizados anteriormente. O app não espera o callback onUserLeaveHint, como faria no Android 11.

    Por exemplo, talvez você queira chamar setPictureInPictureParams na primeira reprodução e em qualquer outra se a proporção for alterada.

  2. Chame setAutoEnterEnabled(false), mas apenas quando necessário. Por exemplo, provavelmente não será ideal entrar no modo PIP se a reprodução atual estiver no estado pausado.

Defina um sourceRectHint adequado para entrar e sair do modo PIP.

Desde a introdução do PiP no Android 8.0, setSourceRectHint indica a área da atividade que fica visível após a transição para o picture-in-picture, por exemplo, os limites de visualização de vídeo em um player de vídeo.

Com o Android 12, o sistema usa sourceRectHint para implementar uma animação muito mais suave ao entrar e sair do modo picture-in-picture.

Para definir corretamente sourceRectHint para entrar e sair do modo PIP:

  1. Crie PictureInPictureParams usando os limites adequados como sourceRectHint. Também recomendamos anexar um listener de mudança de layout ao player de vídeo:
val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, left: Int,
            top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop:
            Int, oldRight: Int, oldBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }

mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
  1. Se necessário, atualize o sourceRectHint antes que o sistema inicie a transição de saída. Quando o sistema está prestes a sair do modo picture-in-picture, a hierarquia de visualização da atividade é definida para a configuração de destino (por exemplo, tela cheia). O app pode anexar um listener de mudança de layout à visualização raiz ou de destino, como a do player de vídeo, para detectar o evento e atualizar a sourceRectHint antes do início da animação.
// 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()
        )
    }
}

Desativar o redimensionamento contínuo de conteúdos que não são de vídeo

O Android 12 adiciona a flag setSeamlessResizeEnabled, que oferece uma animação de transição muito mais suave ao redimensionar conteúdo que não é de vídeo na janela PIP. Antes, o redimensionamento de conteúdo que não era de vídeo em uma janela PiP podia criar artefatos visuais desagradáveis.

Para ativar o redimensionamento contínuo para conteúdo de vídeo:

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

Processar a interface durante o PiP

Quando a atividade entra ou sai do modo picture-in-picture (PiP), o sistema chama Activity.onPictureInPictureModeChanged() ou Fragment.onPictureInPictureModeChanged().

O Android 15 apresenta mudanças que garantem uma transição ainda mais suave ao entrar no modo PIP. Isso é útil para apps que têm elementos de interface sobrepostos à interface principal, que entra no PiP.

Os desenvolvedores usam o callback onPictureInPictureModeChanged() para definir a lógica que alterna a visibilidade dos elementos da interface sobrepostos. Esse callback é acionado quando a animação de entrada ou saída do PiP é concluída. No Android 15, a classe PictureInPictureUiState inclui um novo estado.

Com esse novo estado da interface, os apps destinados ao Android 15 observam o callback Activity#onPictureInPictureUiStateChanged() sendo invocado com isTransitioningToPip() assim que a animação do PiP começa. Há muitos elementos da interface que não são relevantes para o app quando ele está no modo PIP, por exemplo, visualizações ou layouts que incluem informações como sugestões, próximos vídeos, classificações e títulos. Quando o app entrar no modo PIP, use o callback onPictureInPictureUiStateChanged() para ocultar esses elementos da interface. Quando o app entrar no modo de tela cheia pela janela PIP, use o callback onPictureInPictureModeChanged() para reexibir esses elementos, conforme mostrado nos exemplos abaixo:

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
    if (pipState.isTransitioningToPip()) {
        // Hide UI elements.
    }
}
override fun onPictureInPictureModeChanged(
    isInPictureInPictureMode: Boolean,
    newConfig: Configuration
) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
    if (!isInPictureInPictureMode) {
        // Unhide UI elements.
    }
}

Essa alternância rápida de visibilidade de elementos irrelevantes da interface (para uma janela PiP) ajuda a garantir uma animação de entrada PiP mais suave e sem oscilações.

Substitua esses callbacks para redesenhar os elementos da interface da atividade. No modo PIP, sua atividade é mostrada em uma janela pequena. Os usuários não podem interagir com os elementos da interface do app quando ele está no modo picture-in-picture, e os detalhes de pequenos elementos da interface podem ser difíceis de ver. As atividades de reprodução de vídeo com IU mínima proporcionam a melhor experiência do usuário.

Se o app precisar fornecer ações personalizadas para o PiP, consulte Adicionar controles nesta página. Remova outros elementos da interface antes que a atividade entre no PiP e restaure-os quando ela voltar ao modo de tela cheia.

Adicionar controles

A janela de picture-in-picture pode exibir controles quando o usuário abre o menu da janela, tocando nela em um dispositivo móvel ou selecionando o menu no controle remoto da TV.

Se um app tiver uma sessão de mídia ativa, os controles de reprodução, pausa, avanço e retorno vão aparecer.

Também é possível especificar ações personalizadas explicitamente criando PictureInPictureParams com PictureInPictureParams.Builder.setActions() antes de entrar no modo PIP e transmitir os parâmetros ao entrar no modo PIP usando enterPictureInPictureMode(android.app.PictureInPictureParams) ou setPictureInPictureParams(android.app.PictureInPictureParams). Tome cuidado. Se você tentar adicionar mais de getMaxNumPictureInPictureActions(), vai receber apenas o número máximo.

Continuar a reprodução do vídeo no PiP

Quando a atividade muda para o PiP, o sistema a coloca no estado pausado e chama o método onPause(). A reprodução de vídeo não deve ser pausada, mas sim continuar se a atividade for pausada durante a transição para o modo PIP.

No Android 7.0 (nível 24 da API) e versões mais recentes, pause e retome a reprodução de vídeo quando o sistema chamar onStop() e onStart() da atividade. Ao fazer isso, você evita ter que verificar se o app está no modo PIP em onPause() e continuar explicitamente a reprodução.

Se você não tiver definido a flag setAutoEnterEnabled como true e precisar pausar a reprodução na implementação de onPause(), verifique o modo PIP chamando isInPictureInPictureMode() e processe a reprodução adequadamente. Exemplo:

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, don't pause playback.
    if (isInPictureInPictureMode) {
        // Continue playback.
    } else {
        // Use existing playback logic for paused activity behavior.
    }
}

Quando a atividade sai do modo PIP e volta para o modo de tela cheia, o sistema retoma a atividade e chama o método onResume().

Usar uma única atividade de reprodução para o PIP

No seu app, um usuário pode selecionar um novo vídeo ao procurar conteúdo na tela principal, enquanto uma atividade de reprodução de vídeo está no modo picture-in-picture. Reproduza o novo vídeo na atividade de reprodução atual no modo de tela cheia, em vez de iniciar uma nova atividade que possa confundir o usuário.

Para garantir que uma única atividade seja usada para solicitações de reprodução de vídeo e alternada para o modo PIP ou fora dele, conforme necessário, defina o android:launchMode da atividade como singleTask no manifesto:

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

Na sua atividade, substitua onNewIntent() e processe o novo vídeo, interrompendo a reprodução de qualquer vídeo atual, se necessário.

Suporte ao PiP para apps de câmera

Para ativar o PiP em apps de câmera, garanta que a câmera permaneça ativa no modo PiP, não fechando a câmera quando onPause() for chamado:

override fun onPause() {
    super.onPause()
    // Don't close the camera if the app is entering PiP mode
    if (!isInPictureInPictureMode) {
        closeCamera()
    }
}

Como em outros casos de uso, oculte elementos não essenciais da interface (como controles e sobreposições) e adicione ações personalizadas para controlar a câmera (por exemplo, interromper a gravação ou inverter a câmera).

Calcular sourceRectHint para transições suaves

Fornecer um sourceRectHint preciso com as coordenadas exatas da tela do visor da câmera é essencial para uma animação de entrada suave. É possível receber os limites da visualização de prévia usando getGlobalVisibleRect() da seguinte maneira:

val previewView = findViewById<View>(R.id.preview_view)
val globalRect = Rect()
// Ensure the view is laid out before calling getGlobalVisibleRect() to get valid screen coordinates.
previewView.getGlobalVisibleRect(globalRect)
val params = PictureInPictureParams.Builder()
    .setSourceRectHint(globalRect)
    .build()
setPictureInPictureParams(params)

Outras considerações

O modo picture-in-picture pode ser desativado em dispositivos com pouca RAM. Antes que seu app use o modo picture-in-picture, confira se ele está disponível chamando hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

O modo picture-in-picture é destinado a atividades que exibem vídeos em tela cheia. Ao mudar sua atividade para o modo picture-in-picture, evite exibir conteúdo que não seja de vídeo. Acompanhe quando sua atividade entra no modo PIP e oculte elementos da interface, conforme descrito em Como processar a interface durante o PIP.

Quando uma atividade está no modo picture-in-picture, por padrão, ela não recebe o foco de entrada. Para receber eventos de entrada no modo picture-in-picture, use MediaSession.setCallback(). Para ver mais informações sobre o uso de setCallback(), consulte Mostrar um card do Tocando agora.

Quando o app está no modo picture-in-picture, a reprodução de vídeo na janela picture-in-picture pode causar interferência de áudio com outro app, como um app de player de música ou de pesquisa por voz. Para evitar isso, solicite o foco de áudio ao iniciar a reprodução do vídeo e processe as notificações de mudança de foco de áudio, conforme descrito em Como gerenciar o foco de áudio. Se você receber uma notificação de perda de foco de áudio no modo PIP, pause ou interrompa a reprodução do vídeo.

Quando o app estiver prestes a entrar no PiP, observe que apenas a atividade de cima entra no picture-in-picture. Em algumas situações, como em dispositivos com várias janelas, é possível que a atividade de base seja mostrada e fique visível novamente ao lado da atividade de PIP. Trate esse caso de acordo, incluindo a atividade que recebe um callback onResume() ou onPause(). Também é possível que o usuário interaja com a atividade. Por exemplo, se você tiver uma atividade de lista de vídeos exibida e a atividade de reprodução de vídeo no modo PIP, o usuário poderá selecionar um novo vídeo na lista, e a atividade PIP será atualizada de acordo.

Outra amostra de código

Para baixar um app de exemplo escrito em Kotlin, consulte Exemplo de PictureInPicture do Android (Kotlin).