Wyświetlanie multimediów

Interfejsy API android.media.projection wprowadzone w Androidzie 5 (poziom interfejsu API 21) umożliwiają przechwytywanie zawartości wyświetlacza urządzenia w postaci strumienia multimediów, który można odtwarzać, nagrywać lub przesyłać na inne urządzenia, np. telewizory.

Android 14 (poziom interfejsu API 34) wprowadza udostępnianie ekranu aplikacji, które umożliwia użytkownikom udostępnianie pojedynczego okna aplikacji zamiast całego ekranu urządzenia niezależnie od trybu okna. Udostępnianie ekranu aplikacji wyklucza pasek stanu, pasek nawigacji, powiadomienia i inne elementy interfejsu systemu z wyświetlacza udostępnionego – nawet wtedy, gdy udostępnianie ekranu aplikacji jest używane do przechwycenia aplikacji w trybie pełnoekranowym. Udostępniana jest tylko zawartość wybranej aplikacji.

Udostępnianie ekranu aplikacji zapewnia prywatność użytkowników, zwiększa ich produktywność i poprawia wielozadaniowość, ponieważ pozwala użytkownikom uruchamiać wiele aplikacji, ale ogranicza udostępnianie treści do jednej aplikacji.

3 reprezentacje wyświetlania

Projekcja multimediów przechwytuje zawartość ekranu urządzenia lub okna aplikacji, a następnie wyświetla przechwycony obraz na ekranie wirtualnym Surface.

Wyświetlacz rzeczywistego urządzenia wyświetlany na wyświetlaczu wirtualnym Treści wirtualnego wyświetlacza zapisane w ramach interfejsu „Surface” udostępnionego przez aplikację.
Rysunek 1. rzeczywisty ekran urządzenia lub okno aplikacji wyświetlane na wirtualnym ekranie. Wirtualny wyświetlacz zapisany w aplikacjiSurface.

Aplikacja udostępnia Surface za pomocą MediaRecorder, SurfaceTexture lub ImageReader, która pobiera zawartość z przechwyconego wyświetlacza i umożliwia zarządzanie obrazami renderowanymi na Surface w czasie rzeczywistym. Możesz zapisać obrazy jako nagranie lub przesłać je na telewizor lub inne urządzenie.

Prawdziwy wyświetlacz

Rozpocznij sesję wyświetlania multimediów, uzyskując token, który umożliwia Twojej aplikacji rejestrowanie zawartości wyświetlacza urządzenia lub okna aplikacji. Token jest reprezentowany przez wystąpienie klasy MediaProjection.

Użyj metody getMediaProjection() usługi systemowej MediaProjectionManager, aby utworzyć instancję MediaProjection, gdy rozpoczynasz nową aktywność. Aby określić operację przechwytywania ekranu, uruchom aktywność za pomocą intencji z metody createScreenCaptureIntent():

Kotlin

val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection
val startMediaProjection = registerForActivityResult( StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) } }
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())

Java

final MediaProjectionManager mediaProjectionManager =
    getSystemService(MediaProjectionManager.class);
final MediaProjection[] mediaProjection = new MediaProjection[1];
ActivityResultLauncher startMediaProjection = registerForActivityResult( new StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { mediaProjection[0] = mediaProjectionManager .getMediaProjection(result.getResultCode(), result.getData()); } } );
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

Wyświetlacz wirtualny

Głównym elementem projekcji multimediów jest wyświetlacz wirtualny, który tworzysz, wywołując funkcję createVirtualDisplay() w instancji MediaProjection:

Kotlin

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

Java

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null);

Parametry widthheight określają wymiary wirtualnego wyświetlacza. Aby uzyskać wartości szerokości i wysokości, użyj interfejsów API WindowMetrics wprowadzonych w Androidzie 11 (poziom interfejsu API 30). (szczegółowe informacje znajdziesz w sekcji Rozmiar rzutowania multimediów).

Surface

Dostosuj rozmiar powierzchni projekcji multimediów, aby uzyskać wyjście o odpowiedniej rozdzielczości. Ustaw dużą powierzchnię (niską rozdzielczość) do przesyłania ekranu na telewizory lub monitory komputerowe, a małą (wysoką rozdzielczość) do nagrywania wyświetlacza urządzenia.

Od Androida 12L (poziom API 32) podczas renderowania przechwyczonej treści na powierzchni system skaluje ją równomiernie, zachowując współczynnik proporcji, tak aby oba wymiary treści (szerokość i wysokość) były równe lub mniejsze od odpowiednich wymiarów powierzchni. Następnie przechwycone treści są wyśrodkowywane na powierzchni.

Skalowanie w Androidzie 12L poprawia jakość przesyłania obrazu na telewizory i inne duże ekrany, maksymalizując rozmiar obrazu i zapewniając jednocześnie odpowiedni współczynnik proporcji.

Uprawnienia usługi działającej na pierwszym planie

Jeśli Twoja aplikacja jest kierowana na Androida 14 lub nowszą wersję, manifest aplikacji musi zawierać deklarację uprawnień dla typu usługi na pierwszym planie mediaProjection:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

Uruchom usługę przesyłania multimediów, wywołując startForeground().

Jeśli nie określisz typu usługi na pierwszym planie w wywołaniu, typ zostanie domyślnie ustawiony na liczbę binarną z typów usług na pierwszym planie zdefiniowanych w pliku manifestu. Jeśli w pliku manifestu nie ma określonych typów usług, system zwróci błąd MissingForegroundServiceTypeException.

Aplikacja musi prosić o zgodę użytkownika przed każdą sesją wyświetlania multimediów. Sesja to jedno wywołanie funkcji createVirtualDisplay(). Aby nawiązać połączenie, token MediaProjection musi zostać użyty tylko raz.

W Androidzie 14 lub nowszym metoda createVirtualDisplay() powoduje błąd SecurityException, jeśli aplikacja wykonuje jedną z tych czynności:

  • Przekazuje wystąpienie Intent zwracane przez funkcję createScreenCaptureIntent() do funkcji getMediaProjection() więcej niż raz
  • wywołanie funkcji createVirtualDisplay() więcej niż raz w ramach tej samej instancji MediaProjection

Rozmiar wyświetlania multimediów

Projekcja multimediów może obejmować cały ekran urządzenia lub okno aplikacji niezależnie od trybu okna.

Rozmiar początkowy

W przypadku pełnoekranowego wyświetlania multimediów aplikacja musi określić rozmiar ekranu urządzenia. Podczas udostępniania ekranu aplikacji aplikacja nie będzie mogła określić rozmiaru przechwytywanego wyświetlacza, dopóki użytkownik nie wybierze regionu przechwytywania. W związku z tym początkowy rozmiar dowolnej projekcji multimediów jest równy rozmiarowi ekranu urządzenia.

Użyj metody WindowManager getMaximumWindowMetrics() platformy, aby zwrócić obiekt WindowMetrics dla ekranu urządzenia, nawet jeśli aplikacja hostująca projekcję multimediów jest w trybie wielookiennym i zajmuje tylko część ekranu.

Aby zapewnić zgodność z poziomem interfejsu API 14, użyj metody WindowMetricsCalculator computeMaximumWindowMetrics() z biblioteki Jetpack WindowManager.

Aby uzyskać szerokość i wysokość ekranu urządzenia, wywołaj metodę WindowMetrics getBounds().

Zmiany rozmiaru

Rozmiar rzutowania multimediów może się zmienić, gdy użytkownik obróci urządzenie lub wybierze okno aplikacji jako obszar przechwytywania podczas udostępniania ekranu aplikacji. Projekcja multimediów może być w formacie letterbox, jeśli uchwycone treści mają inny rozmiar niż maksymalne wymiary okna uzyskane podczas konfigurowania projekcji multimediów.

Aby zapewnić dokładne dopasowanie rzutowania multimediów do rozmiaru uchwyconego treści w przypadku każdego uchwyconego regionu i przy obracaniu urządzenia, użyj wywołania onCapturedContentResize(), aby zmienić rozmiar uchwytu. (Więcej informacji znajdziesz w sekcji Dostosowywanie).

Dostosowywanie

Aplikacja może dostosować wyświetlanie multimediów do potrzeb użytkownika za pomocą tych interfejsów API: MediaProjection.Callback

  • onCapturedContentVisibilityChanged(): umożliwia aplikacji hosta (aplikacji, która rozpoczęła projekcję multimediów) wyświetlanie lub ukrywanie udostępnionych treści.

    Za pomocą tej funkcji wywołania zwrotnego możesz dostosować interfejs aplikacji na podstawie tego, czy zarejestrowany region jest widoczny dla użytkownika. Jeśli na przykład Twoja aplikacja jest widoczna dla użytkownika i wyświetla przechwycone treści w interfejsie aplikacji, a przechwycona aplikacja jest też widoczna dla użytkownika (co jest sygnalizowane przez ten wywołanie zwrotne), użytkownik widzi te same treści dwukrotnie. Użyj wywołania zwrotnego, aby zaktualizować interfejs użytkownika aplikacji i ukryć uchwycony obraz, zwalniając miejsce na inne treści.

  • onCapturedContentResize(): umożliwia aplikacji hosta zmianę rozmiaru rzutowania multimediów na wyświetlaczu wirtualnym i rzutowanie multimediów Surface w zależności od rozmiaru przechwyconego regionu wyświetlacza.

    Aktywowane, gdy uchwycony obraz – okno pojedynczej aplikacji lub cały ekran urządzenia – zmienia rozmiar (z powodu obracania urządzenia lub przejścia uchwyconej aplikacji w inny tryb okna). Użyj tego interfejsu API, aby zmienić rozmiar zarówno wirtualnego wyświetlacza, jak i powierzchni, tak aby współczynnik proporcji pasował do obrazu, a zapis nie był ograniczony do obszaru letterbox.

Odzyskiwanie zasobów

Aplikacja powinna zarejestrować wywołanie zwrotne MediaProjection onStop(), aby otrzymywać informacje o zatrzymaniu i utracie ważności sesji wyświetlania multimediów. Gdy sesja zostanie zatrzymana, aplikacja powinna zwolnić zasoby, takie jak wirtualny wyświetlacz i powierzchnia projekcji. Zatrzymana sesja wyświetlania multimediów nie może już utworzyć nowego wirtualnego wyświetlacza, nawet jeśli Twoja aplikacja nie utworzyła wcześniej wirtualnego wyświetlacza dla tego wyświetlania multimediów.

System wywołuje tę funkcję, gdy kończy się wyświetlanie multimediów. Może się to zdarzyć z kilku powodów, takich jak:

Jeśli aplikacja nie zarejestruje wywołania zwrotnego, każde wywołanie funkcji createVirtualDisplay() spowoduje błąd IllegalStateException.

Zrezygnuj

Android 14 lub nowszy włącza udostępnianie ekranu aplikacji domyślnie. W ramach każdej sesji wyświetlania multimediów użytkownicy mogą udostępniać okno aplikacji lub cały wyświetlacz.

Aplikacja może wyłączyć udostępnianie ekranu, wywołując metodę createScreenCaptureIntent(MediaProjectionConfig) z argumentem MediaProjectionConfig zwracanym przez wywołanie metody createConfigForDefaultDisplay().

Wywołanie funkcji createScreenCaptureIntent(MediaProjectionConfig) z argumentem MediaProjectionConfig zwracanym przez funkcję createConfigForUserChoice() działa tak samo jak w przypadku działania domyślnego, czyli wywołania funkcji createScreenCaptureIntent().

Aplikacje o zmiennym rozmiarze

Zawsze upewnij się, że aplikacje do wyświetlania multimediów można zmieniać (resizeableActivity="true"). Aplikacje z możliwością zmiany rozmiaru obsługują zmiany konfiguracji urządzenia i tryb wielu okien (patrz Obsługa trybu wielu okien).

Jeśli rozmiary aplikacji nie można zmieniać, musi ona zapytać o zakres wyświetlania w kontekście okna i użyć funkcji getMaximumWindowMetrics(), aby pobrać WindowMetrics maksymalnej dostępnej dla aplikacji powierzchni wyświetlania :

Kotlin

val windowContext = context.createWindowContext(context.display!!,
      WindowManager.LayoutParams.TYPE_APPLICATION, null)
val projectionMetrics = windowContext.getSystemService(WindowManager::class.java)
      .maximumWindowMetrics

Java

Context windowContext = context.createWindowContext(context.getDisplay(),
      WindowManager.LayoutParams.TYPE_APPLICATION, null);
WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class)
      .getMaximumWindowMetrics();

Element na pasku stanu i automatyczne zatrzymywanie

Wykorzystanie funkcji udostępniania ekranu ujawnia prywatne dane użytkowników, takie jak informacje finansowe, ponieważ użytkownicy nie zdają sobie sprawy, że ich ekran jest udostępniany.

Android 15 (poziom interfejsu API 35) i nowsze wersje wyświetlają duży element w pasku stanu, aby poinformować użytkowników o projekcji ekranu. Użytkownicy mogą dotknąć tego elementu, aby zatrzymać udostępnianie, przesyłanie lub nagrywanie ekranu. Ponadto funkcja przesyłania ekranu automatycznie przestaje działać, gdy ekran urządzenia jest zablokowany.

Rysunek 2. Element sterujący w pasku stanu do udostępniania ekranu, przesyłania i nagrywania.

Sprawdzanie dostępności elementu stanu wyświetlania treści na pasku stanu, uruchamiając udostępnianie ekranu, przesyłanie treści lub nagrywanie. Element powinien pojawić się na pasku stanu.

Aby zapewnić, że aplikacja zwalnia zasoby i aktualizuje interfejs użytkownika, gdy użytkownik zatrzyma projekcję ekranu, korzystając z elementu w pasku stanu lub aktywując ekran blokady, wykonaj te czynności:

  • Utwórz instancję MediaProjection.Callback.

  • Zaimplementuj metodę wywołania zwrotnego onStop(). Metoda jest wywoływana, gdy kończy się wyświetlanie na ekranie. Zwalniaj zasoby, które są używane przez Twoją aplikację, i w razie potrzeby aktualizuj interfejs użytkownika aplikacji.

Aby przetestować wywołanie zwrotne, kliknij element na pasku stanu lub zablokuj ekran urządzenia, aby zatrzymać rzutowanie ekranu. Sprawdź, czy wywoływana jest metoda onStop() i czy aplikacja reaguje zgodnie z oczekiwaniami.

Dodatkowe materiały

Więcej informacji o projekcjonowaniu multimediów znajdziesz w artykule Nagrywanie odtwarzania filmów i dźwięku.