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 no sistema.

O picture-in-picture também pode ser usado em dispositivos com o SO Android TV compatível que executem Android 14 (nível 34 da API) ou mais recente. Embora haja muitas semelhanças, considerações adicionais ao usar 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 do picture-in-picture atual e o tamanho máximo ou mínimo de picture-in-picture, por exemplo, tocar duas vezes em uma janela maximizada minimiza e o oposto também é verdadeiro.

  • Para guardar a janela, arraste-a para a borda esquerda ou direita. Para remover da 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 a tela. até em casa. É assim que o Google Maps continua exibindo rotas o usuário executa outra atividade ao mesmo tempo.

  • O aplicativo pode colocar um vídeo no modo picture-in-picture quando o usuário sai da tela de o vídeo para navegar por outros conteúdos.

  • 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 no modo picture-in-picture enquanto o vídeo principal exibe uma atividade de seleção de conteúdo.

Declarar suporte a picture-in-picture

Por padrão, o sistema não é automaticamente compatível com o picture-in-picture para apps. Se você quiser oferecer suporte a picture-in-picture no seu app, registre a atividade de vídeo no manifesto usando definindo android:supportsPictureInPicture como true. Além disso, especifique a atividade processa as mudanças de configuração de layout para que sua atividade não será reinicializado quando ocorrerem mudanças de layout 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

No Android 12 e versões mais recentes, é possível mudar sua atividade para o modo picture-in-picture ao configurar a sinalização setAutoEnterEnabled para true. Com essa configuração, uma atividade alterna automaticamente para o modo picture-in-picture conforme necessário, sem precisar chamar enterPictureInPictureMode() em onUserLeaveHint. E isso tem a um benefício adicional de fornecer transições muito mais suaves. Para mais detalhes, consulte Fazer suaviza a transição da navegação por gestos para o modo picture-in-picture.

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

Em vez disso, inclua uma lógica que alterna uma atividade para o modo picture-in-picture. de entrar 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 estéticas significativas às transições animadas. entre a tela cheia e as janelas picture-in-picture. É altamente recomendável implementar mudanças aplicáveis; Depois disso, essas alterações são dimensionadas automaticamente para em telas grandes, como dobráveis e tablets, sem a necessidade de qualquer trabalho adicional.

Caso seu app não inclua atualizações aplicáveis, as transições do picture-in-picture ainda vão estar funcional, mas as animações são menos refinadas. Por exemplo, a transição de tela cheia para o modo picture-in-picture pode fazer com que a janela picture-in-picture desapareça durante a antes de reaparecer quando a transição for concluída.

Estas mudanças envolvem o seguinte:

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

Consulte a documentação Exemplo PictureInPicture do Kotlin como referência para permitir uma experiência de transição refinada.

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

No Android 12 e versões mais recentes, a flag setAutoEnterEnabled oferece muitos animação mais suave para transição para conteúdo de vídeo no modo picture-in-picture usando gestos navegação, por exemplo, ao deslizar para cima no modo de tela cheia até o início.

Conclua as etapas a seguir para fazer essa mudança e consulte este exemplo para referência:

  1. Usar 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 espera o Callback onUserLeaveHint, como teria feito no Android 11.

    Por exemplo, é possível chamar setPictureInPictureParams no primeira reprodução e qualquer reprodução seguinte se a proporção for alterada.

  3. Chame setAutoEnterEnabled(false), mas só se for necessário. Por exemplo: não convém entrar no modo picture-in-picture se a reprodução estiver pausada. state.

Definir um sourceRectHint adequado para entrar e sair do modo picture-in-picture

A partir do lançamento do picture-in-picture no Android 8.0, o setSourceRectHint indicou a área da atividade que é visível após a transição para picture-in-picture. Por exemplo, os limites de visualização do vídeo em um player de vídeo.

Com o Android 12, o sistema usa sourceRectHint para implementar uma abordagem ao entrar e sair do modo picture-in-picture.

Para definir corretamente sourceRectHint para entrar e sair do modo picture-in-picture:

  1. Criar PictureInPictureParams usando os limites adequados, como sourceRectHint. Também recomendamos anexar um ouvinte de alteração de layout para o 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 o sourceRectHint antes que o sistema inicie o e sair da transição. Quando o sistema está prestes a sair do modo picture-in-picture, o estado visualização é definida com a configuração de destino (por exemplo, tela cheia). O app pode anexar um listener de mudança de layout à visualização raiz. ou visualização de destino (como a visualização do player de vídeo) para detectar o evento e atualize o sourceRectHint antes que a animação comece.

    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 fornece muitas animação de esmaecimento cruzado mais suave ao redimensionar conteúdo que não é de vídeo no picture-in-picture janela. Antes, o redimensionamento de conteúdo que não é de vídeo em uma janela picture-in-picture poderia criar artefatos visuais desagradáveis.

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 interface durante o 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.

Se o app precisar oferecer 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:

Kotlin

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

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 mídia ativa sessão, depois jogar, os controles "Pausar", "Próximo" e "Anterior" serão exibidos.

Também é possível especificar ações personalizadas explicitamente criando PictureInPictureParams com PictureInPictureParams.Builder.setActions() antes de entrar no modo picture-in-picture e 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.

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

Quando sua atividade alterna para o modo picture-in-picture, o sistema coloca a atividade no modo pausado e chama o método método onPause(). Vídeo a reprodução não deve ser pausada e, em vez disso, continuar a reprodução se a atividade for pausado 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ê pode evitar ter que verificar se o app está no modo picture-in-picture em onPause() e continuar a reprodução explicitamente.

Se você não tiver definido a flag setAutoEnterEnabled como true e precisar pause a reprodução na implementação do onPause() e confira o modo picture-in-picture chamando isInPictureInPictureMode() e processar a reprodução adequadamente. 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 sua atividade e chama seu método onResume().

Usar uma única atividade de reprodução para picture-in-picture

No seu app, um usuário pode selecionar um novo vídeo ao navegar pelo conteúdo da tela principal, enquanto uma atividade de reprodução de vídeo está no modo picture-in-picture. Reproduzir o vídeo novo 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 alternada entrar ou sair do modo picture-in-picture, 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 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. Monitorar quando sua atividade entra no modo picture-in-picture e oculta elementos da interface, conforme descrito em Como lidar com a interface durante o 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 mais informações sobre como usar o setCallback(), consulte Exibir um Tocando agora card.

Quando o app está no modo picture-in-picture, a reprodução do vídeo nessa janela pode causar problemas de áudio. interferência em outro app, como um player de música ou um app 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 áudios foco. Se você receber uma notificação de 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 vai entrar picture-in-picture. 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 a Amostra PictureInPicture do Android (Kotlin) (link em inglês).