Usar a biblioteca Picture-in-Picture do Jetpack

A biblioteca do Jetpack Picture-in-Picture (PiP) oferece uma solução simplificada e robusta para que os desenvolvedores de apps Android implementem a funcionalidade PiP, principalmente para apps de reprodução de mídia, comunicação por vídeo e navegação. Ao fornecer uma API unificada, a biblioteca ajuda a eliminar o código boilerplate, bugs comuns no app e melhorar a qualidade geral da experiência do usuário do PiP.

A biblioteca do Jetpack PiP facilita as APIs PiP atuais, abordando vários desafios e inconsistências importantes no ecossistema Android:

  • Fragmentação do SO: a biblioteca processa automaticamente as diferenças nas chamadas de API PiP em várias versões do Android, como o uso de enterPictureInPictureMode antes do Android 12 e isAutoEnterEnabled depois, para que os desenvolvedores não precisem gerenciar diferenças de versão.
  • Parâmetros PiP incorretos: ela oferece uma solução unificada para definir corretamente os parâmetros PiP, por exemplo, setSourceRectHint, para criar animações suaves e de alta qualidade durante a reprodução de mídia.
  • Callbacks de estado PiP unificados: ela consolida onPictureInPictureModeChanged e onPictureInPictureUiStateChanged em uma única interface de callback unificada (PictureInPictureDelegate.OnPictureInPictureEventListener) para simplificar o estado e o gerenciamento da interface.
  • Redução de código boilerplate: a biblioteca reduz a quantidade de código boilerplate repetitivo, oferecendo conjuntos predefinidos de RemoteActions para casos de uso comuns, como controles de reprodução e ações de videochamada.
  • Preparação para o futuro: outros recursos do PiP são fornecidos pela biblioteca do Jetpack, permitindo que os usuários acessem funcionalidades extras com o mínimo ou nenhum esforço.

Workflow de migração

Identifique a categoria de caso de uso do app e a lógica PiP legada:

Categorias:reprodução de vídeo, navegação ou videochamada.

Lógica PiP legada a ser identificada :

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Configuração do AndroidManifest

Verifique se a atividade que entra no PiP declara suporte no AndroidManifest.xml com as configChanges necessárias para evitar reinicializações desnecessárias:

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

3. Configuração do ambiente

Adicione as dependências necessárias ao build.gradle:

dependencies {
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity:1.13.0")
implementation("androidx.core:core-pip:1.0.0-alpha02") }

Use as bibliotecas mais recentes do AndroidX para as dependências e consulte a página de versões para mais informações.

4. Seleção e inicialização de modelos

Escolha o modelo de implementação que melhor se adapta ao caso de uso do app:

  • Navegação e videochamada: BasicPictureInPicture; o redimensionamento contínuo normalmente não é compatível e você não precisa de uma dica de retângulo de origem.
  • Reprodução de vídeo: VideoPlaybackPictureInPicture; rastreia automaticamente os limites da visualização do player para a dica de retângulo de origem e ativa o redimensionamento contínuo por padrão.

Para adotar a biblioteca do Jetpack, substitua a implementação PiP personalizada atual pelas APIs da biblioteca do Jetpack. A complexidade e o custo da adoção variam de acordo com a implementação atual do app.

As seções a seguir descrevem alguns dos casos de uso típicos do PiP e as etapas de implementação necessárias:

O app informa a biblioteca sobre o estado ativo ou inativo da navegação e define a proporção. A biblioteca do Jetpack cuida do restante.

Principais diferenças:

  1. Não é necessário diferenciar a entrada automática e a entrada legada no lado do app.
  2. Interfaces de callback consolidadas.
  3. Novo builder PictureInPictureParams para oferecer compatibilidade com versões anteriores.

Videochamada

O app informa a biblioteca sobre o estado ativo ou inativo da chamada e define a proporção.

Principais diferenças:

  1. Não é necessário diferenciar a entrada automática e a entrada legada no lado do app.
  2. Interfaces de callback consolidadas.
  3. Novo builder PictureInPictureParams para oferecer compatibilidade com versões anteriores.
  4. Ícones de ação padronizados para videochamada.

5. Migração de código

  • Lógica de entrada: substitua a lógica específica da API, como setAutoEnterEnabled para o Android 12 e versões mais recentes ou onUserLeaveHint para o Android 11 e versões anteriores por setEnabled. Acione isso sempre que o status de qualificação do PiP mudar.
  • Callbacks:consolide onPictureInPictureModeChanged (alternância de layout) e onPictureInPictureUiStateChanged (animação/estados) em um callback unificado baseado em eventos onPictureInPictureEvent.
  • Ações e parâmetros: atualize os parâmetros usando setActions e setAspectRatio na instância do modelo sempre que eles mudarem.
  • Processamento especial de vídeo:para apps de vídeo, use setPlayerView para automatizar atualizações de dicas de retângulo de origem e garantir transições suaves. ` ### 6. Limpeza

Para VideoPlaybackPictureInPicture, chame close em onDispose ou onDestroy para liberar recursos como rastreadores de visualização.

Padrões de implementação de referência

Exemplos de implementações.

Navegação e videochamada

class NavOrVideoCallJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: BasicPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = BasicPictureInPicture(this)
        // BasicPictureInPicture is ideal for Navigation and Video call use cases.
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTERED -> { /* Toggle to PiP layout */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Toggle to Full-screen layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* Optional: PiP is stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* Optional: PiP is unstashed */ }
        }
    }
}

Reprodução de vídeo

class VideoPlaybackJpipActivity : ComponentActivity(), PictureInPictureDelegate.OnPictureInPictureEventListener {
    private lateinit var pictureInPictureImpl: VideoPlaybackPictureInPicture
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        pictureInPictureImpl = VideoPlaybackPictureInPicture(this)
        pictureInPictureImpl.addOnPictureInPictureEventListener(
            ContextCompat.getMainExecutor(this),
            this
        )
        setContent {
            ContentScreen(pictureInPictureImpl)
        }
    }
    override fun onPictureInPictureEvent(
        event: PictureInPictureDelegate.Event,
        config: Configuration?
    ) {
        when (event) {
            PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> { /* Hide overlays */ }
            PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> { /* Animation finished */ }
            PictureInPictureDelegate.Event.ENTERED -> { /* Switch to PiP layout */ }
            PictureInPictureDelegate.Event.STASHED -> { /* PiP stashed */ }
            PictureInPictureDelegate.Event.UNSTASHED -> { /* PiP unstashed */ }
            PictureInPictureDelegate.Event.EXITED -> { /* Return to full-screen */ }
        }
    }

    @Composable
    fun ContentScreen(pipController: VideoPlaybackPictureInPicture) {
        DisposableEffect(pipController) {
            onDispose {
                pipController.close()
            }
        }
    }
}