Wyświetlanie multimediów

Interfejsy android.media.projectionAPI wprowadzone w Androidzie 5 (poziom interfejsu API 21) umożliwiają przechwytywanie zawartości wyświetlacza urządzenia jako strumień 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ępniona 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

Projektor multimediów przechwytuje zawartość ekranu urządzenia lub okna aplikacji, a następnie wyświetla przechwycony obraz na ekranie wirtualnym, który renderuje obraz na 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ść przechwyczonej treści 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<Intent> 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 wirtualny wyświetlacz, 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 API 30). (szczegółowe informacje znajdziesz w sekcji Rozmiar obrazu na ekranie projekcyjnym).

Surface

Wymiary 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 przechwyczonej treści, dopóki użytkownik nie wybierze obszaru przechwytywania. Dlatego początkowy rozmiar dowolnej projekcji multimediów jest taki sam jak rozmiar ekranu urządzenia.

Użyj metody platformy WindowManager getMaximumWindowMetrics(), 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 zawartoś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(): Pozwala aplikacji hosta (aplikacji, która rozpoczęła projekcję multimediów) wyświetlać lub ukrywać udostępnione 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, aby ukryć uchwycony obraz i zwolnić miejsce na inne treści.

  • onCapturedContentResize(): umożliwia aplikacji hosta zmianę rozmiaru rzutowania multimediów na wyświetlaczu wirtualnym i rzutowanie multimediów Surface na podstawie 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 uchwyconego materiału, a obraz nie był ujęty w ramkę.

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. Po zakończeniu sesji aplikacja powinna zwolnić zasoby, które posiada, takie jak wirtualny wyświetlacz i powierzchnia projekcji. Zatrzymana sesja wyświetlania multimediów nie może już utworzyć nowego wyświetlacza wirtualnego, nawet jeśli aplikacja nie utworzyła wcześniej wyświetlacza wirtualnego 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 zarejestrowała wywołania zwrotnego, każde wywołanie funkcji createVirtualDisplay() powoduje wyjątek IllegalStateException.

Zrezygnuj

Android 14 lub nowszy włącza domyślnie udostępnianie ekranu aplikacji. Podczas każdej sesji udostępniania multimediów użytkownicy mogą udostępniać okno aplikacji lub cały ekran.

Aplikacja może zrezygnować z udostępniania 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 z wywołania funkcji 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 skalować (resizeableActivity="true"). Aplikacje, które można skalować, obsługują zmiany konfiguracji urządzenia i tryb wielookienkowy (patrz Obsługa trybu wielookienkowego).

Jeśli rozmiar aplikacji nie może być zmieniany, 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) w wersji QPR1 wprowadza nowy element w pasku stanu, który jest duży i wyróżniony, aby informować użytkowników o troszczących się projekcjach ekranu. Użytkownicy mogą kliknąć element, aby zatrzymać udostępnianie, przesyłanie strumieniowe lub nagrywanie ekranu.

W Androidzie 15 QPR1 i nowszych wyświetlanie na drugim ekranie jest automatycznie zatrzymywane, gdy ekran urządzenia jest zablokowany.

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

Dodatkowe materiały

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