Adicionar vídeos usando picture-in-picture (PiP)

A partir do Android 8.0 (API de nível 26), o Android permite que as atividades sejam iniciadas no modo picture-in-picture. Picture-in-picture é um tipo especial de modo de várias janelas usado principalmente para reprodução de vídeos. Ele permite que o usuário assista a um vídeo em uma janela pequena fixada 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 picture-in-picture aparece na camada superior da tela, em um canto escolhido pelo sistema.

O PiP também tem suporte a dispositivos compatíveis com o SO Android TV que executam o Android 14 (nível 34 da API) ou versões mais recentes. 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 e o máximo ou mínimo do picture-in-picture. Por exemplo, tocar duas vezes em uma janela maximizada a minimiza, e o contrário também é verdadeiro.

  • Para guardar a janela, arraste-a para a borda esquerda ou direita. Para retomar a janela, toque na parte visível da janela oculta 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.

  • Seu app pode colocar um vídeo no modo picture-in-picture quando o usuário sai do vídeo para procurar 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 picture-in-picture 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 picture-in-picture no seu app, registre a atividade de vídeo no manifesto definindo android:supportsPictureInPicture como true. Especifique também que a atividade processa as mudanças de configuração de layout para que ela não seja reiniciada quando essas mudanças ocorrerem durante as transições do modo picture-in-picture.

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

Mudar sua atividade para o modo picture-in-picture

A partir do Android 12, você pode mudar a atividade para o modo picture-in-picture definindo a flag setAutoEnterEnabled como true. Com essa configuração, uma atividade passa automaticamente para o modo picture-in-picture conforme necessário, sem precisar chamar explicitamente enterPictureInPictureMode() em onUserLeaveHint. E isso tem a vantagem de fornecer transições muito mais suaves. Para saber mais, consulte Suavizar transições no modo picture-in-picture pela navegação por gestos.

Se você estiver segmentando o Android 11 ou versões anteriores, uma atividade precisa chamar enterPictureInPictureMode() para alternar para o modo PiP. Por exemplo, o código a seguir alterna uma atividade para o modo picture-in-picture quando o usuário clica em um botão dedicado a isso na IU do 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;
    }
    ...
}

É possível incluir uma lógica que alterna uma atividade para o modo picture-in-picture em vez de colocá-la em 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. Para usar esse caso, modifique onUserLeaveHint():

Kotlin

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

Java

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

Recomendado: ofereça aos usuários uma experiência sofisticada de transição de picture-in-picture

O Android 12 adicionou melhorias cosméticas significativas nas transições animadas entre janelas de tela cheia e picture-in-picture. É altamente recomendável implementar todas as mudanças aplicáveis. Depois disso, essas mudanças são dimensionadas automaticamente para telas grandes, como dobráveis e tablets, sem a necessidade de outros trabalhos.

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

Essas mudanças envolvem o seguinte:

  • Suavizar transições no modo picture-in-picture pela navegação por gestos
  • Como definir um sourceRectHint adequado para entrar e sair do modo PiP
  • Como desativar o redimensionamento contínuo de conteúdos que não são de vídeo

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

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

A partir do 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 ir à tela inicial da tela cheia.

Siga as etapas abaixo para fazer essa mudança e consulte este exemplo como referência:

  1. Use setAutoEnterEnabled para criar 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. Chame setPictureInPictureParams usando os PictureInPictureParams atualizados anteriormente. O app não aguarda o callback onUserLeaveHint (como teria feito no Android 11).

    Por exemplo, você pode chamar setPictureInPictureParams na primeira reprodução e em qualquer reprodução seguinte se a proporção mudar.

  3. Chame setAutoEnterEnabled(false), mas apenas quando necessário. Por exemplo, provavelmente você não vai querer entrar no modo picture-in-picture se a reprodução atual estiver no estado pausado.

Definir um sourceRectHint adequado para entrar e sair do modo PiP

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

No 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. Recomendamos também anexar um listener de mudança de layout ao player de vídeo:

    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. Se necessário, atualize a 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 é destinada à 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.

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

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 esmaecimento muito mais suave ao redimensionar conteúdo que não é de vídeo na janela picture-in-picture. Antes, o redimensionamento desse tipo de conteúdo em uma janela picture-in-picture poderia criar artefatos visuais conflitantes.

Para desativar o redimensionamento contínuo de conteúdos que não são de vídeo:

Kotlin

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

Java

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

Processar a IU 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 introduz mudanças que garantem uma transição mais suave ao entrar no modo picture-in-picture. Isso é benéfico 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. A partir do 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 PiP começa. Há muitos elementos de 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 picture-in-picture, use o callback onPictureInPictureUiStateChanged() para ocultar esses elementos da interface. Quando o app entra no modo de tela cheia da janela PiP, use o callback onPictureInPictureModeChanged() para mostrar esses elementos, conforme mostrado nos exemplos a seguir:

Kotlin

override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureUiStateChanged(PictureInPictureUiState pipState) {
        if (pipState.isTransitioningToPip()) {
          // Hide UI elements.
        }
    }

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

Java

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (isInPictureInPictureMode) {
          // Unhide UI elements.
        }
    }

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

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

Caso seu app precise fornecer ações personalizadas para picture-in-picture, consulte Adicionar controles nesta página. Remova outros elementos da IU antes de sua atividade entrar no modo picture-in-picture e restaure-os quando ela voltar à 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 tocar, pausar, próxima e anterior serão exibidos.

Também é possível especificar ações personalizadas de forma explícita criando PictureInPictureParams com PictureInPictureParams.Builder.setActions() antes de entrar no modo picture-in-picture, além de transmitir os parâmetros ao entrar nesse modo usando enterPictureInPictureMode(android.app.PictureInPictureParams) ou setPictureInPictureParams(android.app.PictureInPictureParams). Tenha cuidado. Se você tentar adicionar mais do que getMaxNumPictureInPictureActions(), conseguirá apenas o número máximo.

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

Quando sua atividade alterna para o modo picture-in-picture, o sistema coloca a atividade no estado pausado e chama o método onPause() da atividade. A reprodução de vídeo não poderá ser pausada. Em vez disso, continue se a atividade for pausada durante a transição para o modo picture-in-picture.

No Android 7.0 e versões mais recentes, pause e retome a reprodução do vídeo quando o sistema chamar o onStop() e onStart() da atividade. Ao fazer isso, você evita a necessidade de verificar se o app está no modo picture-in-picture em onPause() e continua a reprodução de forma explícita.

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 gerencie a reprodução de maneira adequada. Exemplo:

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

Quando a atividade sai do modo picture-in-picture 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 navegar pelo conteúdo na tela principal, enquanto uma atividade de reprodução de vídeo estiver no modo picture-in-picture. Reproduza o novo vídeo na atividade de reprodução existente 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 ativada ou desativada no modo picture-in-picture, defina o android:launchMode da atividade como singleTask no seu manifesto:

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

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

Práticas recomendadas

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 picture-in-picture e oculte elementos da interface, conforme descrito em Como gerenciar a interface durante o picture-in-picture.

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 saber mais sobre como usar setCallback(), consulte Mostrar um card "Tocando agora".

Quando o app está no modo picture-in-picture, a reprodução de vídeo na janela PiP 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 a seleção de áudio quando começar a reproduzir o vídeo e processe notificações de mudança de seleção de áudio, conforme descrito em Gerenciar a seleção de áudio. Se você receber uma notificação sobre perda de seleção de áudio quando estiver no modo picture-in-picture, pause ou pare a reprodução do vídeo.

Quando o app estiver prestes a entrar no picture-in-picture, apenas a atividade principal entrará nesse modo. Em algumas situações, como em dispositivos com várias janelas, é possível que a atividade abaixo seja exibida e se torne visível novamente junto com a atividade do modo picture-in-picture. Gerencie esse caso adequadamente, incluindo a atividade abaixo, que recebe um callback onResume() ou onPause(). Também é possível que o usuário interaja com a atividade. Por exemplo, se uma atividade de lista de vídeos estiver sendo exibida e a atividade de reprodução de vídeo estiver no modo picture-in-picture, o usuário poderá selecionar um novo vídeo da lista, e essa atividade será atualizada de acordo.

Outra amostra de código

Para fazer o download de um app de exemplo criado em Kotlin, consulte Amostra PictureInPicture do Android (Kotlin) (link em inglês).