メディア プロジェクション

Android 5(API レベル 21)で導入された android.media.projection API を使用すると、デバイス ディスプレイのコンテンツをメディア ストリームとしてキャプチャし、テレビなどの他のデバイスで再生、録画、キャストできます。

Android 14(API レベル 34)では、アプリ画面共有が導入され、ウィンドウ モードに関係なく、デバイス画面全体ではなく 1 つのアプリ ウィンドウを共有できるようになりました。アプリの画面共有では、アプリの画面共有を使用してアプリを全画面でキャプチャする場合でも、ステータスバー、ナビゲーション バー、通知、その他のシステム UI 要素が共有ディスプレイから除外されます。選択したアプリのコンテンツのみが共有されます。

アプリの画面共有は、ユーザーが複数のアプリを実行できるようにしつつ、コンテンツの共有を 1 つのアプリに限定することで、ユーザーのプライバシーを確保し、ユーザーの生産性を高め、マルチタスクを強化します。

3 つの表示表現

メディア プロジェクションは、デバイス ディスプレイまたはアプリ ウィンドウのコンテンツをキャプチャし、キャプチャした画像を仮想ディスプレイに投影して Surface にレンダリングします。

仮想ディスプレイに実際のデバイス ディスプレイを投影する。仮想ディスプレイのコンテンツをアプリが提供する「サーフェス」に書き込む。
図 1. 仮想ディスプレイに投影された実際のデバイスの画面またはアプリ ウィンドウ。アプリが提供する Surface に仮想ディスプレイが書き込まれます。

アプリは、MediaRecorderSurfaceTexture、または ImageReader によって Surface を提供します。これにより、キャプチャされたディスプレイのコンテンツが使用され、Surface にレンダリングされる画像をリアルタイムで管理できます。画像は録画として保存することも、テレビなどのデバイスにキャストすることもできます。

リアル ディスプレイ

メディア プロジェクション セッションを開始するには、デバイスのディスプレイまたはアプリ ウィンドウのコンテンツをキャプチャする権限をアプリに付与するトークンを取得します。トークンは、MediaProjection クラスのインスタンスで表されます。

MediaProjectionManager システム サービスの getMediaProjection() メソッドを使用して、新しいアクティビティの開始時に MediaProjection インスタンスを作成します。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);

width パラメータと height パラメータは、仮想ディスプレイのサイズを指定します。幅と高さの値を取得するには、Android 11(API レベル 30)で導入された WindowMetrics API を使用します。(詳しくは、メディア プロジェクション サイズをご覧ください)。

画面

メディア プロジェクション サーフェスのサイズを調整して、適切な解像度で出力を生成する。テレビやパソコンのモニターに画面をキャストする場合はサーフェスを大きく(低解像度)、デバイスのディスプレイを録画する場合はサーフェスを小さく(高解像度)にします。

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() の 1 回の呼び出しです。MediaProjection トークンは呼び出しの際に 1 回だけ使用する必要があります。

Android 14 以降では、アプリが次のいずれかを行うと、createVirtualDisplay() メソッドは SecurityException をスローします。

  • createScreenCaptureIntent() から返された Intent インスタンスを getMediaProjection() に複数回渡します。
  • 同じ MediaProjection インスタンスで createVirtualDisplay() を複数回呼び出す

メディア プロジェクション サイズ

メディア プロジェクションでは、ウィンドウ モードに関係なく、デバイスのディスプレイ全体またはアプリ ウィンドウをキャプチャできます。

初期サイズ

全画面メディア プロジェクションでは、アプリでデバイス画面のサイズを決定する必要があります。アプリの画面共有では、ユーザーがキャプチャ領域を選択するまで、アプリはキャプチャされたディスプレイのサイズを特定できません。そのため、メディア プロジェクションの初期サイズはデバイスの画面サイズになります。

メディア プロジェクション ホストアプリがマルチウィンドウ モードでディスプレイの一部のみを占有している場合でも、プラットフォームの WindowManager getMaximumWindowMetrics() メソッドを使用して、デバイス画面の WindowMetrics オブジェクトを返します。

API レベル 14 までとの互換性を確保するには、Jetpack WindowManager ライブラリの WindowMetricsCalculator computeMaximumWindowMetrics() メソッドを使用します。

WindowMetricsgetBounds() メソッドを呼び出して、デバイス ディスプレイの幅と高さを取得します。

サイズの変更

メディア プロジェクションのサイズは、デバイスが回転されたとき、またはユーザーがアプリ画面共有でキャプチャ領域としてアプリ ウィンドウを選択したときに変更される可能性があります。キャプチャしたコンテンツのサイズが、メディア プロジェクションのセットアップ時に取得された最大ウィンドウ指標と異なる場合、メディア プロジェクションがレターボックス表示されることがあります。

キャプチャした領域やデバイスの回転をまたいで、メディア プロジェクションをキャプチャしたコンテンツのサイズに正確に合わせるには、onCapturedContentResize() コールバックを使用してキャプチャのサイズを変更します。(詳細については、後述のカスタマイズのセクションをご覧ください)。

カスタマイズ

アプリでは、次の MediaProjection.Callback API を使用して、メディア プロジェクションのユーザー エクスペリエンスをカスタマイズできます。

  • onCapturedContentVisibilityChanged(): ホストアプリ(メディア プロジェクションを開始したアプリ)が、共有コンテンツを表示または非表示にできるようにします。

    このコールバックを使用して、キャプチャした領域がユーザーに表示されるかどうかに基づいてアプリの UI をカスタマイズします。たとえば、アプリがユーザーに表示され、キャプチャされたコンテンツがアプリの UI 内に表示され、キャプチャされたアプリがユーザーにも表示されている場合(このコールバックで示されている場合)、ユーザーには同じコンテンツが 2 回表示されます。コールバックを使用してアプリの 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();

参考情報

メディア プロジェクションの詳細については、動画と音声の再生のキャプチャをご覧ください。