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

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

Android 14(API レベル 34)では、アプリ画面共有が導入されました。これにより、ウィンドウ モードに関係なく、デバイスの画面全体ではなく、単一のアプリ ウィンドウを共有できるようになりました。アプリ画面共有では、アプリ画面共有を使用してアプリを全画面でキャプチャする場合でも、ステータスバー、ナビゲーション バー、通知などのシステム 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 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 を使用します。(詳細については、メディア プロジェクションのサイズのセクションをご覧ください)。

Surface

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

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() メソッドを使用します。

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

サイズの変更

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

キャプチャされた領域とデバイスの回転に関係なく、キャプチャされたコンテンツのサイズとメディア プロジェクションが正確に一致するようにするには、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();

ステータスバーのチップと自動停止

画面投影の悪用は、デバイスの画面が共有されていることにユーザーが気付かないため、財務情報などの機密データが漏洩する可能性があります。

Android 15(API レベル 35)以降では、進行中の画面投影をユーザーに知らせるために、大きく目立つステータスバー チップが表示されます。ユーザーはチップをタップして、画面の共有、キャスト、録画を停止できます。また、デバイスの画面がロックされると、画面の投影は自動的に停止します。

図 2. 画面共有、キャスト、録画用のステータスバー チップ。

画面共有、キャスト、録画の開始によって、メディア プロジェクションのステータスバー チップの可用性をテストします。ステータスバーにチップが表示されます。

ユーザーがステータスバー チップを操作したり、ロック画面を有効にしたりすることで画面投影が停止されたときに、アプリがリソースを解放して UI を更新するようにするには、次の操作を行います。

  • MediaProjection.Callback のインスタンスを作成します。

  • コールバック onStop() メソッドを実装します。このメソッドは、画面投影が停止したときに呼び出されます。アプリが保持しているリソースを解放し、必要に応じてアプリ UI を更新します。

コールバックをテストするには、ステータスバー チップをタップするか、デバイスの画面をロックして画面投影を停止します。onStop() メソッドが呼び出され、アプリが意図したとおりに応答することを確認します。

参考情報

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