Dodawanie filmów przy użyciu obrazu w obrazie (PIP)

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak obsługiwać tryb obrazu w obrazie w Compose.

Od Androida 8.0 (poziom API 26) aktywności mogą być uruchamiane w trybie obraz w obrazie. Obraz w obrazie to specjalny tryb wielookienkowy, który jest najczęściej używany do odtwarzania filmów. Umożliwia użytkownikowi oglądanie filmu w małym oknie przypiętym do rogu ekranu, a jednocześnie przełączanie innych aplikacji lub wyszukiwanie treści na głównym ekranie.

Tryb obrazu w obrazie korzysta z interfejsów API obsługujących wiele okien, które są dostępne w Androidzie 7.0, aby wyświetlać przypięte okno nakładki wideo. Aby dodać do aplikacji tryb obrazu w obrazie, musisz zarejestrować działania, które go obsługują, w razie potrzeby przełączyć działanie w tryb obrazu w obrazie i upewnić się, że elementy interfejsu są ukryte, a odtwarzanie wideo jest kontynuowane, gdy działanie jest w trybie obrazu w obrazie.

Okno PiP pojawi się na wierzchu ekranu w rogu wybranym przez system.

Tryb obraz w obrazie jest też obsługiwany na zgodnych urządzeniach z Androidem TV OS z Androidem 14 (API na poziomie 34) lub nowszym. Chociaż istnieje wiele podobieństw, podczas korzystania z obrazu w obrazie na telewizorze należy wziąć pod uwagę dodatkowe kwestie.

Jak użytkownicy mogą wchodzić w interakcję z oknem PiP

Użytkownicy mogą przeciągnąć okno PiP w inne miejsce. Od Androida 12 użytkownicy mogą też:

  • Kliknij okno, aby wyświetlić przełącznik pełnego ekranu, przycisk zamykania, przycisk ustawień i niestandardowe działania udostępniane przez aplikację (np. elementy sterujące odtwarzaniem).

  • Kliknij dwukrotnie okno, aby przełączać się między bieżącym rozmiarem obrazu w obrazie a maksymalnym lub minimalnym rozmiarem obrazu w obrazie. Na przykład kliknięcie dwukrotne zmaksymalizowanego okna spowoduje jego zminimalizowanie i odwrotnie.

  • Schowaj okno, przeciągając je do lewej lub prawej krawędzi. Aby przywrócić okno, kliknij widoczną część schowanego okna lub przeciągnij je.

  • Zmień rozmiar okna PIP, używając gestu uszczypnięcia.

Aplikacja kontroluje, kiedy bieżąca aktywność przechodzi w tryb obrazu w obrazie. Oto kilka przykładów:

  • Aktywność może przejść w tryb obrazu w obrazie, gdy użytkownik naciśnie przycisk ekranu głównego lub przesunie palcem w górę do ekranu głównego. Dzięki temu Mapy Google mogą nadal wyświetlać wskazówki, gdy użytkownik wykonuje w tym samym czasie inne czynności.

  • Aplikacja może przenieść film do trybu obraz w obrazie, gdy użytkownik wróci z filmu, aby przeglądać inne treści.

  • Aplikacja może przełączyć film w tryb obrazu w obrazie, gdy użytkownik ogląda koniec odcinka. Na ekranie głównym wyświetlają się informacje promocyjne lub podsumowujące dotyczące następnego odcinka serialu.

  • Aplikacja może umożliwiać użytkownikom dodawanie do kolejki dodatkowych treści podczas oglądania filmu. Film będzie nadal odtwarzany w trybie obrazu w obrazie, a na ekranie głównym będzie wyświetlana aktywność wyboru treści.

Deklarowanie obsługi trybu obraz w obrazie

Domyślnie system nie obsługuje automatycznie trybu obraz w obrazie w przypadku aplikacji. Jeśli chcesz, aby Twoja aplikacja obsługiwała tryb obraz w obrazie, zarejestruj aktywność związaną z wideo w pliku manifestu, ustawiając wartość android:supportsPictureInPicture na true. Określ też, że Twoja aktywność obsługuje zmiany konfiguracji układu, aby nie była ponownie uruchamiana, gdy podczas przejść do trybu obrazu w obrazie nastąpią zmiany układu.

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

Przełączanie aktywności na tryb obrazu w obrazie

Od Androida 12 możesz przełączyć aktywność w tryb obrazu w obrazie, ustawiając flagę setAutoEnterEnabled na true. Dzięki temu ustawieniu aktywność automatycznie przełącza się w tryb obrazu w obrazie w razie potrzeby bez konieczności jawnego wywoływania enterPictureInPictureMode()onUserLeaveHint. Dodatkową korzyścią jest płynniejsze przechodzenie między scenami. Więcej informacji znajdziesz w artykule Płynniejsze przechodzenie do trybu obrazu w obrazie podczas nawigacji gestami.

Jeśli kierujesz reklamy na Androida 11 lub starszego, aktywność musi wywołać enterPictureInPictureMode() aby przejść do trybu obrazu w obrazie. Na przykład poniższy kod przełącza aktywność w tryb obrazu w obrazie, gdy użytkownik kliknie w interfejsie aplikacji specjalny przycisk:

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

Możesz uwzględnić logikę, która przełącza aktywność w tryb obrazu w obrazie zamiast przenosić ją w tle. Na przykład Mapy Google przełączają się w tryb obrazu w obrazie, gdy użytkownik naciśnie przycisk Początek lub Ostatnie podczas nawigacji w aplikacji. Możesz przechwycić ten przypadek, zastępując onUserLeaveHint():

Kotlin

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

Java

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

Zalecane: zapewnij użytkownikom płynne przejście do trybu obrazu w obrazie

W Androidzie 12 wprowadziliśmy znaczące zmiany wizualne w animowanych przejściach między oknami pełnoekranowymi a oknami w trybie obraz w obrazie. Zdecydowanie zalecamy wprowadzenie wszystkich odpowiednich zmian. Po ich wprowadzeniu zostaną one automatycznie dostosowane do dużych ekranów, takich jak urządzenia składane i tablety, bez konieczności wykonywania dodatkowych czynności.

Jeśli Twoja aplikacja nie zawiera odpowiednich aktualizacji, przejścia do trybu „obraz w obrazie” nadal będą działać, ale animacje będą mniej dopracowane. Na przykład przejście z trybu pełnoekranowego do trybu obrazu w obrazie może spowodować zniknięcie okna obrazu w obrazie podczas przejścia, zanim pojawi się ponownie po jego zakończeniu.

Zmiany te obejmują:

  • Płynniejsze przechodzenie do trybu obrazu w obrazie podczas korzystania z nawigacji gestami
  • Ustawienie odpowiedniego sourceRectHint do włączania i wyłączania trybu obrazu w obrazie
  • Wyłączanie płynnej zmiany rozmiaru w przypadku treści innych niż filmy

Aby uzyskać więcej informacji o włączaniu płynnego przejścia, zapoznaj się z przykładem Kotlin PictureInPicture na Androidzie.

Płynniejsze przechodzenie do trybu obrazu w obrazie podczas korzystania z nawigacji gestami

Od Androida 12 flaga setAutoEnterEnabled zapewnia znacznie płynniejszą animację podczas przechodzenia do treści wideo w trybie obrazu w obrazie za pomocą nawigacji gestami, np. podczas przesuwania palcem w górę do ekranu głównego z trybu pełnoekranowego.

Aby wprowadzić tę zmianę, wykonaj te czynności. W celu uzyskania dodatkowych informacji zapoznaj się z tym przykładem:

  1. Użyj setAutoEnterEnabled, aby utworzyć 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. Zadzwoń do setPictureInPictureParams z aktualnym numerem PictureInPictureParams z wyprzedzeniem. Aplikacja nie czeka na wywołanie zwrotne onUserLeaveHint (jak w Androidzie 11).

    Możesz na przykład wywołać funkcję setPictureInPictureParams przy pierwszym odtworzeniu i przy każdym kolejnym, jeśli zmieni się format obrazu.

  3. Wywołuj funkcję setAutoEnterEnabled(false) tylko wtedy, gdy jest to konieczne. Na przykład nie chcesz włączać trybu obrazu w obrazie, jeśli odtwarzanie jest wstrzymane.

Ustawiono odpowiedni sourceRectHint do włączania i wyłączania trybu obrazu w obrazie.

Od wprowadzenia funkcji obraz w obrazie w Androidzie 8.0 parametr setSourceRectHint wskazywał obszar aktywności widoczny po przejściu do trybu obraz w obrazie, np. granice widoku filmu w odtwarzaczu wideo.

W Androidzie 12 system używa sourceRectHint, aby wdrożyć znacznie płynniejszą animację zarówno podczas włączania, jak i wyłączania trybu obraz w obrazie.

Aby prawidłowo ustawić sourceRectHint do włączania i wyłączania trybu obrazu w obrazie:

  1. Skonstruuj PictureInPictureParams, używając odpowiednich granic jako sourceRectHint. Zalecamy też dołączenie do odtwarzacza wideo detektora zmiany układu:

    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. W razie potrzeby zaktualizuj sourceRectHint, zanim system rozpocznie przejście do stanu wyjścia. Gdy system ma wyjść z trybu obrazu w obrazie, hierarchia widoków aktywności jest układana w konfiguracji docelowej (np. na pełnym ekranie). Aplikacja może dołączyć detektor zmiany układu do widoku głównego lub widoku docelowego (np. widoku odtwarzacza wideo), aby wykrywać zdarzenie i aktualizować wartość sourceRectHint przed rozpoczęciem animacji.

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

Wyłączanie płynnej zmiany rozmiaru treści innych niż filmy

Android 12 dodaje flagę setSeamlessResizeEnabled, która zapewnia znacznie płynniejszą animację przenikania podczas zmiany rozmiaru treści innych niż wideo w oknie obrazu w obrazie. Wcześniej zmiana rozmiaru treści innych niż wideo w oknie PIP mogła powodować nieprzyjemne artefakty wizualne.

Aby włączyć płynną zmianę rozmiaru treści wideo:

Kotlin

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

Java

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

Obsługa interfejsu podczas korzystania z trybu obrazu w obrazie

Gdy aktywność wchodzi w tryb obraz w obrazie lub z niego wychodzi, system wywołuje metodę Activity.onPictureInPictureModeChanged() lub Fragment.onPictureInPictureModeChanged().

W Androidzie 15 wprowadzono zmiany, które zapewniają jeszcze płynniejsze przejście do trybu obrazu w obrazie. Jest to korzystne w przypadku aplikacji, które mają elementy interfejsu użytkownika nakładane na główny interfejs użytkownika, który przechodzi w tryb obrazu w obrazie.

Deweloperzy używają wywołania zwrotnego onPictureInPictureModeChanged() do definiowania logiki, która przełącza widoczność nakładanych elementów interfejsu. To wywołanie zwrotne jest wywoływane po zakończeniu animacji wejścia lub wyjścia z trybu obrazu w obrazie. Od Androida 15 klasa PictureInPictureUiState zawiera nowy stan.

W tym nowym stanie interfejsu aplikacje kierowane na Androida 15 obserwują wywołanie wywołania zwrotnego Activity#onPictureInPictureUiStateChanged() z wartością isTransitioningToPip() natychmiast po rozpoczęciu animacji PiP. Wiele elementów interfejsu nie jest istotnych dla aplikacji w trybie obrazu w obrazie, np. widoki lub układy zawierające informacje takie jak sugestie, nadchodzące filmy, oceny i tytuły. Gdy aplikacja przejdzie w tryb obrazu w obrazie, użyj wywołania zwrotnego onPictureInPictureUiStateChanged(), aby ukryć te elementy interfejsu. Gdy aplikacja przechodzi do trybu pełnoekranowego z okienka obrazu w obrazie, użyj wywołania zwrotnego onPictureInPictureModeChanged(), aby odkryć te elementy, jak pokazano w przykładach poniżej:

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

Szybkie przełączanie widoczności nieistotnych elementów interfejsu (w przypadku okna obrazu w obrazie) pomaga zapewnić płynniejszą animację wejścia w tryb obrazu w obrazie bez migotania.

Zastąp te wywołania zwrotne, aby ponownie narysować elementy interfejsu aktywności. Pamiętaj, że w trybie obrazu w obrazie Twoja aktywność jest wyświetlana w małym oknie. Użytkownicy nie mogą wchodzić w interakcje z elementami interfejsu aplikacji, gdy jest ona w trybie obrazu w obrazie, a szczegóły małych elementów interfejsu mogą być trudne do zobaczenia. Odtwarzanie filmów z minimalnym interfejsem zapewnia największą wygodę.

Jeśli Twoja aplikacja musi udostępniać niestandardowe działania w trybie obraz w obrazie, zapoznaj się z sekcją Dodawanie elementów sterujących na tej stronie. Usuń inne elementy interfejsu przed przejściem działania w tryb obrazu w obrazie i przywróć je, gdy działanie ponownie zajmie cały ekran.

Dodaj elementy sterujące

Gdy użytkownik otworzy menu okna (dotykając okna na urządzeniu mobilnym lub wybierając menu na pilocie telewizora), mogą się w nim pojawić elementy sterujące.

Jeśli aplikacja ma aktywną sesję multimedialną, pojawią się elementy sterujące odtwarzaniem, wstrzymywaniem, następnym i poprzednim.

Możesz też określić działania niestandardowe, tworząc PictureInPictureParams za pomocą PictureInPictureParams.Builder.setActions() przed przejściem do trybu obrazu w obrazie i przekazując parametry podczas przechodzenia do tego trybu za pomocą enterPictureInPictureMode(android.app.PictureInPictureParams) lub setPictureInPictureParams(android.app.PictureInPictureParams). Zachowaj ostrożność. Jeśli spróbujesz dodać więcej niż getMaxNumPictureInPictureActions(), otrzymasz tylko maksymalną liczbę.

Kontynuowanie odtwarzania filmu w trybie obraz w obrazie

Gdy aktywność przechodzi w tryb obrazu w obrazie, system umieszcza ją w stanie wstrzymania i wywołuje metodę onPause() aktywności. Odtwarzanie wideo nie powinno być wstrzymywane, ale powinno być kontynuowane, jeśli aktywność zostanie wstrzymana podczas przechodzenia do trybu obrazu w obrazie.

W Androidzie 7.0 i nowszych należy wstrzymywać i wznawiać odtwarzanie wideo, gdy system wywołuje metody aktywności onStop()onStart(). Dzięki temu nie musisz sprawdzać, czy aplikacja jest w trybie obrazu w obrazie w onPause(), i wyraźnie kontynuować odtwarzania.

Jeśli nie ustawisz flagi setAutoEnterEnabled na true i musisz wstrzymać odtwarzanie w implementacji onPause(), sprawdź tryb obrazu w obrazie, wywołując isInPictureInPictureMode(), i odpowiednio obsłuż odtwarzanie. Na przykład:

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

Gdy aktywność przełączy się z trybu obrazu w obrazie z powrotem na tryb pełnoekranowy, system wznowi aktywność i wywoła metodę onResume().

Używanie jednej aktywności odtwarzania w trybie obraz w obrazie

W aplikacji użytkownik może wybrać nowy film podczas przeglądania treści na ekranie głównym, gdy aktywność odtwarzania filmu jest w trybie obrazu w obrazie. Odtwarzaj nowy film w ramach istniejącej aktywności odtwarzania w trybie pełnoekranowym, zamiast uruchamiać nową aktywność, która może zmylić użytkownika.

Aby mieć pewność, że do żądań odtwarzania wideo jest używana jedna aktywność, która w razie potrzeby przełącza się w tryb obrazu w obrazie i z niego wychodzi, ustaw w pliku manifestu atrybut android:launchMode aktywności na singleTask:

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

W aktywności zastąp onNewIntent() i obsłuż nowy film, w razie potrzeby zatrzymując odtwarzanie bieżącego filmu.

Sprawdzone metody

Funkcja obrazu w obrazie może być wyłączona na urządzeniach z niewielką ilością pamięci RAM. Zanim aplikacja zacznie korzystać z trybu obrazu w obrazie, sprawdź, czy jest on dostępny, wywołując funkcję hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).

Tryb obrazu w obrazie jest przeznaczony do działań, które odtwarzają wideo na pełnym ekranie. Podczas przełączania aktywności w tryb obrazu w obrazie unikaj wyświetlania czegokolwiek poza treściami wideo. śledzić, kiedy aktywność przechodzi w tryb obrazu w obrazie, i ukrywać elementy interfejsu zgodnie z opisem w sekcji Obsługa interfejsu podczas korzystania z trybu obrazu w obrazie.

Gdy aktywność jest w trybie obrazu w obrazie, domyślnie nie uzyskuje ona fokusu. Aby otrzymywać zdarzenia wejściowe w trybie obrazu w obrazie, użyj MediaSession.setCallback(). Więcej informacji o używaniu setCallback() znajdziesz w artykule Wyświetlanie karty „Teraz odtwarzane”.

Gdy aplikacja jest w trybie obraz w obrazie, odtwarzanie wideo w oknie obrazu w obrazie może powodować zakłócenia dźwięku w innej aplikacji, np. w odtwarzaczu muzyki lub aplikacji do wyszukiwania głosowego. Aby tego uniknąć, poproś o skupienie dźwięku, gdy zaczniesz odtwarzać film, i obsługuj powiadomienia o zmianie skupienia dźwięku zgodnie z opisem w artykule Zarządzanie skupieniem dźwięku. Jeśli w trybie obrazu w obrazie otrzymasz powiadomienie o utracie fokusu dźwięku, wstrzymaj lub zatrzymaj odtwarzanie filmu.

Gdy aplikacja ma przejść do trybu obrazu w obrazie, tylko aktywność na górze przechodzi do tego trybu. W niektórych sytuacjach, np. na urządzeniach z wieloma oknami, aktywność poniżej może być teraz widoczna i ponownie pojawić się obok aktywności w trybie obraz w obrazie. W takim przypadku należy podjąć odpowiednie działania, w tym wywołać funkcję zwrotną onResume() lub onPause(). Możliwe jest też, że użytkownik będzie wchodzić w interakcje z aktywnością. Jeśli na przykład wyświetlasz aktywność listy filmów i aktywność odtwarzania filmu w trybie obraz w obrazie, użytkownik może wybrać nowy film z listy, a aktywność w trybie obraz w obrazie powinna się odpowiednio zaktualizować.

Dodatkowy przykładowy kod

Aby pobrać przykładową aplikację napisaną w Kotlinie, zobacz Android PictureInPicture Sample (Kotlin).