Korzystanie z biblioteki Jetpack Picture-in-Picture

Biblioteka Jetpack Picture-in-Picture (PiP) to uproszczone i niezawodne rozwiązanie, które umożliwia deweloperom aplikacji na Androida implementowanie funkcji PiP, zwłaszcza w przypadku aplikacji do odtwarzania multimediów, komunikacji wideo i nawigacji. Dzięki ujednoliconemu interfejsowi API biblioteka pomaga wyeliminować powtarzalny kod, typowe błędy w aplikacji i poprawić ogólną jakość obsługi PiP.

Biblioteka Jetpack PiP ułatwia korzystanie z dotychczasowych interfejsów API PiP, rozwiązując kilka kluczowych problemów i niespójności w ekosystemie Androida:

  • Fragmentacja systemu operacyjnego: biblioteka automatycznie obsługuje różnice w wywołaniach interfejsu API PiP w różnych wersjach Androida, np. używa funkcji enterPictureInPictureMode w wersjach starszych niż Android 12 i isAutoEnterEnabled w nowszych, dzięki czemu deweloperzy nie muszą zarządzać różnicami między wersjami .
  • Nieprawidłowe parametry PiP: biblioteka zapewnia ujednolicone rozwiązanie do prawidłowego ustawiania parametrów PiP, np. setSourceRectHint, aby tworzyć płynne i wysokiej jakości animacje podczas odtwarzania multimediów.
  • Ujednolicone wywołania zwrotne stanu PiP: biblioteka łączy wywołania zwrotne onPictureInPictureModeChanged i onPictureInPictureUiStateChanged w jeden ujednolicony interfejs wywołania zwrotnego (PictureInPictureDelegate.OnPictureInPictureEventListener), co upraszcza zarządzanie stanem i interfejsem.
  • Ograniczenie powtarzalnego kodu: biblioteka zmniejsza ilość powtarzalnego kodu, oferując predefiniowane zestawy RemoteActions na potrzeby typowych przypadków użycia, takich jak elementy sterujące odtwarzaniem i działania związane z rozmowami wideo.
  • Przyszłościowe rozwiązanie: kolejne funkcje PiP są udostępniane za pomocą biblioteki Jetpack, co pozwala użytkownikom uzyskiwać dostęp do dodatkowych funkcji przy minimalnym wysiłku.

Przepływ migracji

Określ kategorię przypadku użycia aplikacji i starszą logikę PiP:

Kategorie: odtwarzanie wideo, nawigacja lub rozmowa wideo.

Starsza logika PiP do zidentyfikowania:

  • onUserLeaveHint
  • setAutoEnterEnabled
  • onPictureInPictureModeChanged
  • onPictureInPictureUiStateChanged
  • setPictureInPictureParams.

2. Konfiguracja pliku AndroidManifest.xml

Upewnij się, że aktywność wchodząca w tryb PiP deklaruje obsługę w pliku AndroidManifest.xml z niezbędnymi elementami configChanges, aby zapobiec niepotrzebnym restartom:

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

3. Konfiguracja środowiska

Dodaj wymagane zależności do pliku 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") }

W przypadku zależności użyj najnowszych bibliotek AndroidX i zapoznaj się z informacjami na stronie wersji.

4. Wybór i inicjowanie szablonu

Wybierz szablon implementacji, który najlepiej pasuje do przypadku użycia aplikacji:

  • Nawigacja i rozmowa wideo: BasicPictureInPicture; płynna zmiana rozmiaru zwykle nie jest obsługiwana i nie potrzebujesz wskazówki dotyczącej prostokąta źródłowego.
  • Odtwarzanie wideo: VideoPlaybackPictureInPicture; automatycznie śledzi granice widoku odtwarzacza na potrzeby wskazówki dotyczącej prostokąta źródłowego i domyślnie włącza płynną zmianę rozmiaru.

Aby zastosować bibliotekę Jetpack, zastąp dotychczasową niestandardową implementację PiP interfejsami API biblioteki Jetpack. Złożoność i koszt wdrożenia będą się różnić w zależności od bieżącej implementacji aplikacji.

W sekcjach poniżej opisujemy niektóre typowe przypadki użycia PiP i niezbędne kroki implementacji:

Aplikacja informuje bibliotekę o stanie aktywnym lub nieaktywnym nawigacji i ustawia współczynnik proporcji. Biblioteka Jetpack zajmie się resztą.

Najważniejsze różnice:

  1. Nie musisz rozróżniać automatycznego i starszego trybu wchodzenia w tryb PiP po stronie aplikacji.
  2. Ujednolicone interfejsy wywołań zwrotnych.
  3. Nowy konstruktor PictureInPictureParams na potrzeby zgodności wstecznej.

Rozmowa wideo

Aplikacja informuje bibliotekę o stanie aktywnym lub nieaktywnym połączenia i ustawia współczynnik proporcji.

Najważniejsze różnice:

  1. Nie musisz rozróżniać automatycznego i starszego trybu wchodzenia w tryb PiP po stronie aplikacji.
  2. Ujednolicone interfejsy wywołań zwrotnych.
  3. Nowy konstruktor PictureInPictureParams na potrzeby zgodności wstecznej.
  4. Ustandaryzowane ikony działań w przypadku rozmów wideo.

5. Migracja kodu

  • Logika wejścia: zastąp logikę specyficzną dla interfejsu API, np. setAutoEnterEnabled w przypadku Androida 12 i nowszych wersji lub onUserLeaveHint w przypadku Androida 11 i starszych wersji funkcją setEnabled. Wywołuj tę funkcję za każdym razem, gdy zmieni się stan kwalifikacji do PiP.
  • Wywołania zwrotne: połącz wywołania zwrotne onPictureInPictureModeChanged (przełączanie układu) i onPictureInPictureUiStateChanged (animacja/stany) w jedno wywołanie zwrotne oparte na zdarzeniach onPictureInPictureEvent.
  • Działania i parametry: aktualizuj parametry za pomocą funkcji setActions i setAspectRatio w instancji szablonu za każdym razem, gdy się zmienią.
  • Specjalna obsługa wideo: w przypadku aplikacji wideo użyj funkcji setPlayerView aby zautomatyzować aktualizacje wskazówki dotyczącej prostokąta źródłowego i zapewnić płynne przejścia. ` ### 6. Czyszczenie

W przypadku VideoPlaybackPictureInPicture wywołaj funkcję close w onDispose lub onDestroy, aby zwolnić zasoby, takie jak śledzenie widoków.

Wzorce implementacji referencyjnej

Przykłady implementacji.

Nawigacja i rozmowa wideo

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 */ }
        }
    }
}

Odtwarzanie wideo

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