Compatibilidade com picture-in-picture

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 do picture-in-picture é exibida na camada superior da tela, em um canto definido pelo sistema.

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.

  • Tocar duas vezes na janela para alternar entre o tamanho atual e o máximo do picture-in-picture. Ocultar a janela arrastando-a para a borda esquerda ou direita. Para exibi-la, basta tocar na parte visível da janela oculta ou arrastá-la 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 (no modo de navegação de botões) ou desliza para cima (no modo de navegação por gestos). É 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 a um vídeo. O vídeo continuará em exibição no modo picture-in-picture enquanto a tela principal mostra uma atividade de seleção de conteúdo.

Declarar compatibilidade com o modo picture-in-picture

Por padrão, o sistema não é automaticamente compatível com o picture-in-picture para apps. Se você quiser oferecer compatibilidade com o picture-in-picture no seu app, registre a atividade de vídeo no manifesto configurando android:supportsPictureInPicture como true. Especifique também que sua atividade gerencia 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"
    ...

Como alternar sua atividade para o modo picture-in-picture

Para entrar no modo picture-in-picture, as atividades precisam chamar enterPictureInPictureMode(). 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;
    }
    ...
}

Recomendamos que você inclua 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();
    }
}

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

A partir do Android 12, você pode usar a sinalização setAutoEnterEnabled para fornecer transições mais suaves para o modo picture-in-picture ao deslizar para cima no modo de navegação por gestos.

Para implementar esse recurso:

  1. Use setAutoEnterEnabled para construir PictureInPictureParams.Builder, desta maneira:

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. Chame setPictureInPictureParams usando os PictureInPictureParams atualizados anteriormente. O app não pode aguardar o callback onUserLeaveHint, como faria no Android 11.

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

  3. Chame setAutoEnterEnabled(false) conforme necessário. Por exemplo, provavelmente não será ideal para um app de vídeo entrar no modo picture-in-picture se a reprodução atual estiver no estado pausado.

Gerenciar a IU no picture-in-picture

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

Modifique esses callbacks para redesenhar os elementos de IU da atividade. Lembre-se de que sua atividade é exibida em uma pequena janela no modo picture-in-picture. 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. As atividades de reprodução de vídeo com IU mínima proporcionam a melhor experiência do usuário.

Caso seu app precise fornecer ações personalizadas para picture-in-picture, consulte Adicionar controles neste documento. Remova outros elementos da IU antes de sua atividade entrar no modo picture-in-picture e restaure-os quando ela voltar à tela cheia:

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture 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 picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

Compatibilidade com animações mais suaves ao sair do modo picture-in-picture

A partir do Android 12, a sinalização SourceRectHint agora é reutilizada para implementar uma animação mais suave ao sair do modo picture-in-picture. Ao sair do modo, o sistema cria a animação usando a sourceRectHint atual disponível, seja ela igual ao Rect original usado para entrar no modo picture-in-picture ou um Rect atualizado fornecido pelo app.

Para implementar esse recurso, atualize o app desta forma:

  1. Continue a construir PictureInPictureParams usando os sourceRectHint e a aspectRatio para uma animação de entrada suave.

  2. Se necessário, atualize a sourceRectHint antes do sistema iniciar 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());
        }
    });
    

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.

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

O Android 12 adiciona a sinalização setSeamlessResizeEnabled, que fornece uma animação de esmaecimento muito mais suave ao redimensionar conteúdo que não é de vídeo na janela do modo picture-in-picture. Anteriormente, o redimensionamento desse tipo de conteúdo em uma janela picture-in-picture poderia criar artefatos visuais conflitantes.

A sinalização setSeamlessResizeEnabled está definida como true por padrão para compatibilidade com versões anteriores. Deixe essa opção definida como true para conteúdo de vídeo e mude para false se o conteúdo não for de vídeo.

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

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

Como continuar a reprodução de vídeo no modo picture-in-picture

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 do vídeo não será pausada e continuará se a atividade for pausada no 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ê tiver que pausar a reprodução na sua implementação de onPause(), verifique o modo picture-in-picture chamando isInPictureInPictureMode() e gerencie a reprodução de maneira adequada, por 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 ao modo de tela cheia, o sistema retoma sua atividade e chama o método onResume().

Como usar uma única atividade de reprodução para o modo picture-in-picture

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. Abra o novo vídeo na atividade de reprodução existente no modo de tela cheia em vez de iniciar uma nova atividade que poderia 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. Rastreie quando sua atividade entra no modo picture-in-picture e oculte elementos da IU, conforme descrito em Como gerenciar a IU no modo 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 ver mais informações sobre o uso de setCallback(), consulte Mostrar um card do Tocando agora.

Quando seu app estiver no modo picture-in-picture, a reprodução de vídeo nessa janela poderá causar interferência de áudio com outro app, como um de player de música ou de pesquisa por voz. Para evitar isso, solicite a seleção de áudio quando iniciar o vídeo e gerencie as notificações para mudança de seleção de áudio, conforme descrito em Como 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 seu 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.

Outro exemplo de código

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