미디어 프로젝션

Android 5 (API 수준 21)에 도입된 android.media.projection API를 사용하면 기기 디스플레이의 콘텐츠를 TV와 같은 다른 기기로 재생, 녹화 또는 전송할 수 있는 미디어 스트림으로 캡처할 수 있습니다.

Android 14 (API 수준 34)에는 사용자가 창 모드와 관계없이 전체 기기 화면이 아닌 단일 앱 창을 공유할 수 있는 앱 화면 공유가 도입되었습니다. 앱 화면 공유는 앱 화면 공유를 사용하여 전체 화면으로 앱을 캡처하는 경우에도 상태 표시줄, 탐색 메뉴, 알림 및 기타 시스템 UI 요소를 공유 디스플레이에서 제외합니다. 선택한 앱의 콘텐츠만 공유됩니다.

앱 화면 공유는 사용자가 여러 앱을 실행할 수 있지만 콘텐츠 공유는 단일 앱으로 제한하여 사용자 개인 정보 보호를 보장하고, 사용자 생산성을 높이고, 멀티태스킹을 향상합니다.

세 가지 디스플레이 표현

미디어 프로젝션은 기기 디스플레이 또는 앱 창의 콘텐츠를 캡처한 다음 캡처된 이미지를 Surface에 이미지를 렌더링하는 가상 디스플레이에 투사합니다.

가상 디스플레이에 프로젝션된 실제 기기 디스플레이 애플리케이션에서 제공하는 `Surface`에 기록된 가상 디스플레이의 콘텐츠
그림 1. 가상 디스플레이에 프로젝션된 실제 기기 화면 또는 앱 창 애플리케이션에서 제공하는 Surface에 작성된 가상 디스플레이입니다.

애플리케이션은 MediaRecorder, SurfaceTexture 또는 ImageReader를 통해 Surface를 제공합니다. 이러한 도구는 캡처된 디스플레이의 콘텐츠를 사용하고 Surface에서 렌더링된 이미지를 실시간으로 관리할 수 있게 해줍니다. 이미지를 녹화 파일로 저장하거나 TV 또는 다른 기기로 전송할 수 있습니다.

실제 디스플레이

앱이 기기 디스플레이 또는 앱 창의 콘텐츠를 캡처할 수 있도록 허용하는 토큰을 가져와 미디어 프로젝션 세션을 시작합니다. 토큰은 MediaProjection 클래스의 인스턴스로 표현됩니다.

새 활동을 시작할 때 MediaProjection 인스턴스를 만들려면 MediaProjectionManager 시스템 서비스의 getMediaProjection() 메서드를 사용합니다. 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());

가상 디스플레이

미디어 프로젝션의 중심은 가상 디스플레이로, MediaProjection 인스턴스에서 createVirtualDisplay()를 호출하여 만듭니다.

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

widthheight 매개변수는 가상 디스플레이의 크기를 지정합니다. 너비와 높이 값을 가져오려면 Android 11 (API 수준 30)에 도입된 WindowMetrics API를 사용하세요. 자세한 내용은 미디어 프로젝션 크기 섹션을 참고하세요.

표시 경로

적절한 해상도로 출력을 생성할 수 있도록 미디어 프로젝션 표면의 크기를 조절합니다. TV나 컴퓨터 모니터로 화면을 전송할 때는 표면을 크게 (저해상도) 설정하고 기기 디스플레이 녹화에서는 표면을 작게 (고해상도) 만듭니다.

Android 12L (API 수준 32)부터, 캡처된 콘텐츠를 노출 영역에 렌더링할 때 시스템은 콘텐츠의 크기를 균일하게 조정하여 가로세로 비율을 유지하므로 콘텐츠의 두 크기 (너비 및 높이)가 해당하는 노출 영역의 크기와 같거나 이보다 작거나 같습니다. 그러면 캡처된 콘텐츠가 노출 영역의 중앙에 배치됩니다.

Android 12L 크기 조정 접근 방식은 적절한 가로세로 비율을 보장하면서 노출 영역 이미지 크기를 최대화하여 텔레비전 및 기타 대형 디스플레이로의 화면 전송을 개선합니다.

포그라운드 서비스 권한

앱이 Android 14 이상을 타겟팅하는 경우 앱 매니페스트에 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>

startForeground()를 호출하여 미디어 프로젝션 서비스를 시작합니다.

호출에서 포그라운드 서비스 유형을 지정하지 않으면 유형이 기본적으로 매니페스트에서 정의된 포그라운드 서비스 유형의 비트 정수로 설정됩니다. 매니페스트에서 서비스 유형을 지정하지 않으면 시스템에서 MissingForegroundServiceTypeException이 발생합니다.

앱은 각 미디어 프로젝션 세션 전에 사용자 동의를 요청해야 합니다. 세션은 createVirtualDisplay()의 단일 호출입니다. MediaProjection 토큰은 호출 시 한 번만 사용해야 합니다.

Android 14 이상에서는 앱이 다음 중 하나를 실행하면 createVirtualDisplay() 메서드에서 SecurityException이 발생합니다.

  • createScreenCaptureIntent()에서 반환된 Intent 인스턴스를 두 번 이상 getMediaProjection()에 전달합니다.
  • 동일한 MediaProjection 인스턴스에서 createVirtualDisplay()를 두 번 이상 호출합니다.

미디어 프로젝션 크기

미디어 프로젝션은 윈도잉 모드와 관계없이 전체 기기 디스플레이 또는 앱 창을 캡처할 수 있습니다.

초기 크기

전체 화면 미디어 프로젝션을 사용하면 앱에서 기기 화면의 크기를 결정해야 합니다. 앱 화면 공유에서는 사용자가 캡처 영역을 선택할 때까지 앱이 캡처된 디스플레이의 크기를 확인할 수 없습니다. 따라서 모든 미디어 프로젝션의 초기 크기는 기기 화면의 크기입니다.

미디어 프로젝션 호스트 앱이 멀티 윈도우 모드에 있고 디스플레이의 일부만 차지하더라도 플랫폼 WindowManager getMaximumWindowMetrics() 메서드를 사용하여 기기 화면의 WindowMetrics 객체를 반환합니다.

API 수준 14까지의 호환성을 위해 Jetpack WindowManager 라이브러리의 WindowMetricsCalculator computeMaximumWindowMetrics() 메서드를 사용합니다.

WindowMetrics getBounds() 메서드를 호출하여 기기 디스플레이의 너비와 높이를 가져옵니다.

크기 변경사항

미디어 프로젝션의 크기는 기기가 회전하거나 사용자가 앱 화면 공유에서 캡처 영역으로 앱 창을 선택할 때 변경될 수 있습니다. 캡처된 콘텐츠의 크기가 미디어 프로젝션을 설정할 때 얻은 최대 창 측정항목과 다른 경우 미디어 프로젝션이 레터박스될 수 있습니다.

미디어 프로젝션이 캡처된 영역과 기기 회전 시 캡처된 콘텐츠의 크기와 정확하게 일치하도록 하려면 onCapturedContentResize() 콜백을 사용하여 캡처 크기를 조절합니다. 자세한 내용은 아래의 맞춤설정 섹션을 참고하세요.

맞춤설정

앱은 다음 MediaProjection.Callback API를 사용하여 미디어 프로젝션 사용자 환경을 맞춤설정할 수 있습니다.

  • onCapturedContentVisibilityChanged(): 호스트 앱 (미디어 프로젝션을 시작한 앱)이 공유 콘텐츠를 표시하거나 숨길 수 있도록 사용 설정합니다.

    이 콜백을 사용하여 캡처된 영역이 사용자에게 표시되는지 여부에 따라 앱의 UI를 맞춤설정합니다. 예를 들어 앱이 사용자에게 표시되고 앱 UI 내에 캡처된 콘텐츠를 표시하며 캡처된 앱도 사용자에게 표시되는 경우 (이 콜백을 통해 표시된 대로) 사용자에게는 동일한 콘텐츠가 두 번 표시됩니다. 콜백을 사용하여 앱의 UI를 업데이트하여 캡처된 콘텐츠를 숨기고 앱에서 다른 콘텐츠를 위한 레이아웃 공간을 확보하세요.

  • onCapturedContentResize(): 호스트 앱이 캡처된 디스플레이 영역의 크기에 따라 가상 디스플레이 및 미디어 프로젝션 Surface의 미디어 프로젝션 크기를 변경할 수 있도록 사용 설정합니다.

    캡처된 콘텐츠(단일 앱 창 또는 전체 기기 디스플레이)가 크기를 변경할 때마다 트리거됩니다(기기 회전 또는 캡처된 앱이 다른 윈도잉 모드로 전환됨). 이 API를 사용하여 가상 디스플레이와 표면의 크기를 조절하여 가로세로 비율이 캡처된 콘텐츠와 일치하고 캡처가 레터박스 처리되지 않도록 합니다.

리소스 복구

앱은 MediaProjection onStop() 콜백을 등록하여 가상 디스플레이 및 프로젝션 표면과 같이 앱에서 보유한 리소스를 해제해야 합니다.

콜백은 미디어 프로젝션이 종료되거나 사용자가 캡처 세션을 계속하는 데 동의하지 않을 때 호출됩니다.

앱이 콜백을 등록하지 않고 사용자가 미디어 프로젝션 세션에 동의하지 않으면 createVirtualDisplay() 호출에서 IllegalStateException이 발생합니다.

노출 알림 시스템 선택 해제

Android 14 이상에서는 기본적으로 앱 화면 공유를 사용 설정합니다. 각 미디어 프로젝션 세션은 사용자에게 앱 창 또는 전체 디스플레이를 공유하는 옵션을 제공합니다.

createConfigForDefaultDisplay() 호출에서 반환된 MediaProjectionConfig 인수로 createScreenCaptureIntent(MediaProjectionConfig) 메서드를 호출하여 앱에서 앱 화면 공유를 선택 해제할 수 있습니다.

createConfigForUserChoice() 호출에서 반환된 MediaProjectionConfig 인수를 사용하여 createScreenCaptureIntent(MediaProjectionConfig)를 호출하는 것은 createScreenCaptureIntent() 호출인 기본 동작과 동일합니다.

크기 조절 가능한 앱

항상 미디어 프로젝션 앱의 크기를 조절할 수 있도록 합니다 (resizeableActivity="true"). 크기 조절 가능한 앱은 기기 설정 변경 및 멀티 윈도우 모드를 지원합니다 (멀티 윈도우 지원 참고).

앱의 크기를 조절할 수 없으면 창 컨텍스트에서 디스플레이 경계를 쿼리하고 getMaximumWindowMetrics()를 사용하여 앱에서 사용할 수 있는 최대 디스플레이 영역의 WindowMetrics를 검색해야 합니다.

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

추가 리소스

미디어 프로젝션에 관한 자세한 내용은 동영상 및 오디오 재생 캡처를 참고하세요.