ピクチャー イン ピクチャー(PIP)のサポート

Android 8.0(API レベル 26)以降では、アクティビティをピクチャー イン ピクチャー(PIP)モードで起動できます。PIP は特別なタイプのマルチウィンドウ モードで、主に動画の再生に使用されます。ユーザーは、メイン画面でアプリ間を移動したりコンテンツをブラウジングしたりしながら、画面の隅に固定された小さなウィンドウで動画を視聴し続けることができます。

PIP は、Android 7.0 で使用可能になったマルチウィンドウ API を利用して、固定された動画オーバーレイ ウィンドウを提供します。アプリで PIP を使用するには、PIP をサポートするアクティビティを登録し、必要に応じてアクティビティを PIP モードに切り替える必要があります。そして、アクティビティが PIP モードのときには、UI 要素を非表示にして動画の再生を継続するようにします。

PIP ウィンドウは、画面の最上位レイヤーの、システムによって選択された隅の領域に表示されます。

ユーザーが PIP ウィンドウを操作する方法

ユーザーは、PIP ウィンドウを別の位置にドラッグできます。Android 12 以降では、次のことも行えます。

  • PIP ウィンドウをシングルタップすると、全画面表示への切り替え、閉じるボタン、設定ボタン、アプリが提供するカスタム アクション(再生コントロールなど)が表示されます。

  • PIP ウィンドウをダブルタップすると、現在の PIP サイズと最大 PIP サイズを切り替えられます。PIP ウィンドウを左端または右端にドラッグすることで退避状態にできます。ウィンドウの退避を解除するには、退避中のウィンドウの表示されている部分をタップするか、ドラッグして引き出します。

  • ピンチ操作で PIP ウィンドウのサイズを変更できます。

現在のアクティビティが PIP モードに入るタイミングは、アプリで制御します。次に例を示します。

  • ユーザーがホームボタンをタップ(ボタン ナビゲーション モード)するか、または画面を下から上にスワイプ(ジェスチャー ナビゲーション モード)してホーム画面に移動したら、アクティビティを PIP モードに切り替えます(Google マップは、ユーザーが別のアクティビティを同時に実行している間、この方法でルートを表示し続けます)。

  • ユーザーが動画から他のコンテンツに移動してブラウジングを開始したら、動画を PIP モードに切り替えます。

  • ユーザーが見ている動画コンテンツがエピソードの終盤に差し掛かったら、動画を PIP モードに切り替えます。メイン画面には、シリーズの次のエピソードに関する宣伝情報やあらすじを表示します。

  • ユーザーが動画を見ながら他のコンテンツをキューに追加できるようにします。PIP モードで動画を再生し続けながら、メイン画面にコンテンツ選択アクティビティを表示します。

ピクチャー イン ピクチャーのサポートを宣言する

デフォルトでは、システムはアプリによる PIP の使用を自動的にサポートすることはありません。アプリで PIP をサポートするには、android:supportsPictureInPicturetrue に設定することにより、動画アクティビティをマニフェストに登録します。また、PIP モードへの遷移中にレイアウト変更が発生した場合でもアクティビティが再起動されないようにするため、アクティビティがレイアウト構成の変更を処理するように指定します。

<activity android:name="VideoActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation"
    ...

アクティビティをピクチャー イン ピクチャーに切り替える

ピクチャー イン ピクチャー モードに切り替えるには、アクティビティから enterPictureInPictureMode() を呼び出す必要があります。次のコード例では、ユーザーがアプリの UI の専用ボタンをクリックしたときに、アクティビティを PIP モードに切り替えています。

Kotlin

override fun onActionClicked(action: Action) {
    if (action.id.toInt() == R.id.lb_control_picture_in_picture) {
        activity?.enterPictureInPictureMode()
        return
    }
}

Java

@Override
public void onActionClicked(Action action) {
    if (action.getId() == R.id.lb_control_picture_in_picture) {
        getActivity().enterPictureInPictureMode();
        return;
    }
    ...
}

アクティビティをバックグラウンドに移行する代わりに PIP モードに切り替えるロジックを含めることもできます。たとえば、Google マップは、ユーザーがホームボタンまたは最近ボタンを押して別のアプリに移動すると、PIP モードに切り替わります。このようなケースをキャッチするには、次のように onUserLeaveHint() をオーバーライドします。

Kotlin

override fun onUserLeaveHint() {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode()
    }
}

Java

@Override
public void onUserLeaveHint () {
    if (iWantToBeInPipModeNow()) {
        enterPictureInPictureMode();
    }
}

ジェスチャー ナビゲーションでの PIP モードへの遷移をよりスムーズにする

Android 12 以降では、setAutoEnterEnabled フラグを使用して、ジェスチャー ナビゲーション モードで上にスワイプしてホーム画面に戻る際の、PIP モードへの遷移をスムーズにできます。

この機能を実装するには以下を行います。

  1. 次のように、setAutoEnterEnabled を使用して PictureInPictureParams.Builder を作成します。

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. 最新の PictureInPictureParams を使って、早い段階で setPictureInPictureParams を呼び出します。これにより、アプリが onUserLeaveHint コールバックを待たないようにします(Android 11 ではコールバックを待っていました)。

    たとえば、アプリは最初の再生と、アスペクト比が変更された場合は以降のすべての再生で、setPictureInPictureParams を呼び出す可能性があります。

  3. 必要に応じて setAutoEnterEnabled(false) を呼び出します。たとえば、動画アプリではほとんどの場合、現在の再生が一時停止状態の場合に PIP モードに入ることは最適ではありません。

ピクチャー イン ピクチャー時に UI を処理する

アクティビティがピクチャー イン ピクチャー モードに入るかピクチャー イン ピクチャー モードから抜けると、システムは Activity.onPictureInPictureModeChanged() または Fragment.onPictureInPictureModeChanged() を呼び出します。

アプリでは、これらのコールバックをオーバーライドして、アクティビティの UI 要素を再描画する必要があります。PIP モードでは、アクティビティを表示するウィンドウが小さいことに留意してください。アプリが PIP モードのため UI 要素が小さくて詳細が見づらいと、ユーザーはアプリの UI 要素を操作できません。動画再生アクティビティでは、UI の数を最小限にすることがユーザー エクスペリエンスの向上につながります。

アプリで PIP 用のカスタム アクションを提供する必要がある場合は、このドキュメントのコントロールを追加するをご覧ください。他の UI 要素は、アクティビティを PIP に切り替える前に削除し、全画面表示に戻すときに復元するようにします。

Kotlin

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
    }
}

Java

@Override
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
    if (isInPictureInPictureMode) {
        // Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

PIP モードの終了時のスムーズなアニメーションのサポート

Android 12 以降では、SourceRectHint フラグを再利用して、PIP モードの終了時のスムーズなアニメーションを実装できます。PIP モードの終了時に、その時点で使用可能な sourceRectHint を使用してアニメーションが作成されます(PIP に入るために使用された元の Rect であるか、アプリが提供する更新された Rect であるかは問いません)。

この機能を実装するには、次のようにアプリを更新します。

  1. スムーズな開始アニメーションを実現するために、引き続き sourceRectHintaspectRatio を使用して PictureInPictureParams を作成します。

  2. 必要に応じて、システムが終了遷移を開始する前に sourceRectHint を更新します。システムが PIP モードを終了しようとすると、アクティビティのビュー階層が遷移先の構成(全画面など)に配置されます。アプリは、レイアウト変更リスナーをルートビューやターゲット ビュー(動画プレーヤー ビューなど)にアタッチすることで、イベントを検出し、アニメーションの開始前に sourceRectHint を更新できます。

    Kotlin

    // Listener is called immediately after the user exits PiP but before animating.
    playerView.addOnLayoutChangeListener { _, left, top, right, bottom,
                         oldLeft, oldTop, oldRight, oldBottom ->
        if (left != oldLeft || right != oldRight || top != oldTop
                || bottom != oldBottom) {
           // The playerView's bounds changed, update the source hint rect to
           // reflect its new bounds.
           val sourceRectHint = Rect()
           playerView.getGlobalVisibleRect(sourceRectHint)
           setPictureInPictureParams(
               PictureInPictureParams.Builder()
                   .setSourceRectHint(sourceRectHint)
                   .build()
           )
        }
    }
    

    Java

    // Listener is called right after the user exits PiP but before
    // animating.
    playerView.addOnLayoutChangeListener((v, left, top, right, bottom,
                         oldLeft, oldTop, oldRight, oldBottom) -> {
        if (left != oldLeft || right != oldRight || top != oldTop
                || bottom != oldBottom) {
           // The playerView’s bounds changed, update the source hint rect to
           // reflect its new bounds.
           final Rect sourceRectHint = new Rect();
           playerView.getGlobalVisibleRect(sourceRectHint);
           setPictureInPictureParams(
                   new PictureInPictureParams.Builder()
                   .setSourceRectHint(sourceRectHint)
                   .build());
        }
    });
    

コントロールを追加する

PIP ウィンドウでは、ユーザーが(モバイル デバイスでウィンドウをタップするか、テレビリモコンでメニューを選択することにより)ウィンドウのメニューを開いたときに、コントロールを表示できます。

アプリにアクティブなメディア セッションがある場合は、「再生」、「一時停止」、「次へ」、「前へ」の各コントロールが表示されます。

また、あらかじめ PictureInPictureParams.Builder.setActions()PictureInPictureParams を設定してカスタム アクションを明示的に指定しておき、PIP モードに入るときに enterPictureInPictureMode(android.app.PictureInPictureParams) または setPictureInPictureParams(android.app.PictureInPictureParams) を使用してそのパラメータを渡すこともできます。ここで注意が必要なのは、getMaxNumPictureInPictureActions() を超える数のアクションを追加しようとしても、最大数のアクションしか追加できないという点です。

動画以外のコンテンツのシームレスなサイズ変更を無効にする

Android 12 では、setSeamlessResizeEnabled フラグが追加されました。このフラグにより、PIP ウィンドウで動画以外のコンテンツのサイズを変更する際に、よりスムーズなクロスフェード アニメーションを提供できるようになりました。以前は、PIP ウィンドウで動画以外のコンテンツのサイズを変更すると、ユーザーに不快感を与える視覚的アーティファクトが発生していました。

下位互換性を維持するため、setSeamlessResizeEnabled フラグはデフォルトで true に設定されています。動画コンテンツの場合は true に設定されたままにして、動画以外のコンテンツの場合は false に変更します。

動画以外のコンテンツのシームレスなサイズ変更を無効にするには、次のようにします。

  setPictureInPictureParams(new PictureInPictureParams.Builder()
          .setSeamlessResizeEnabled(false)
          .build());

ピクチャー イン ピクチャー時に動画再生を続行する

アクティビティが PIP に切り替わるとき、システムはアクティビティを一時停止状態にして、アクティビティの onPause() メソッドを呼び出します。PIP モード中にアクティビティが一時停止された場合は、動画再生を一時停止せず、再生を続行する必要があります。

Android 7.0 以降では、システムがアクティビティの onStop() または onStart() を呼び出したとき、アプリで動画再生の一時停止または再開を行う必要があります。これにより、onPause() でアプリが PIP モードかどうかを確認したり、明示的に再生を続行したりする必要がなくなります。

onPause() の実装で再生を一時停止する必要がある場合は、isInPictureInPictureMode() を呼び出して PIP モードかどうかを確認し、再生を適切に処理します。次に例を示します。

Kotlin

override fun onPause() {
    super.onPause()
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode) {
        // Continue playback
    } else {
        // Use existing playback logic for paused Activity behavior.
    }
}

Java

@Override
public void onPause() {
    // If called while in PiP mode, do not pause playback
    if (isInPictureInPictureMode()) {
        // Continue playback
        ...
    } else {
        // Use existing playback logic for paused Activity behavior.
        ...
    }
}

アクティビティが PIP モードから全画面モードに戻ると、システムはアクティビティを再開して onResume() を呼び出します。

ピクチャー イン ピクチャー用に 1 つの再生アクティビティを使用する

動画再生アクティビティが PIP モードのときに、ユーザーがメイン画面でコンテンツをブラウジングして新しい動画を選択することがあります。その場合は、新しいアクティビティを起動するとユーザーを混乱させる可能性があるため、既存の再生アクティビティを全画面モードにして新しい動画を再生します。

動画再生リクエストに対して必ず 1 つのアクティビティが使用され、必要に応じて PIP モードの開始と終了が切り替えられるようにするには、次のように、マニフェストでアクティビティの android:launchModesingleTask に設定します。

<activity android:name="VideoActivity"
    ...
    android:supportsPictureInPicture="true"
    android:launchMode="singleTask"
    ...

アクティビティでは、onNewIntent() をオーバーライドして新しい動画を処理し、必要に応じて既存の動画再生を停止します。

おすすめの方法

RAM が少ないデバイスでは、PIP が無効になっている場合があります。アプリで PIP を使用する前に、hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) を呼び出して PIP が使用可能であることを確認してください。

PIP は、フルスクリーン動画を再生するアクティビティを対象としています。アクティビティを PIP モードに切り替えるときは、動画以外のコンテンツを表示しないようにしてください。ピクチャー イン ピクチャー時の UI の処理で説明しているように、アクティビティが PIP モードに入るタイミングをトラッキングして UI 要素を非表示にします。

アクティビティが PIP モードのとき、デフォルトでは入力フォーカスを取得しません。PIP モードのときに入力イベントを受信するには、MediaSession.setCallback() を使用します。setCallback() の使用方法について詳しくは、「この曲なに?」カードを表示するをご覧ください。

アプリが PIP モードのとき、PIP ウィンドウで動画を再生することにより、別のアプリ(音楽プレーヤー アプリや音声検索アプリなど)が音声干渉を受ける可能性があります。これを回避するには、動画再生を開始するときに音声フォーカスをリクエストし、音声フォーカスの管理の説明にあるとおり、音声フォーカス変更通知を処理します。PIP モード中に音声フォーカスを失ったという通知を受け取った場合は、動画再生を一時停止または停止します。

アプリが PIP に入る際には、最上位のアクティビティのみがピクチャー イン ピクチャー モードに入ることに注意してください。ある種の状況では(マルチウィンドウ デバイスなど)、下位のアクティビティが表示されて PIP アクティビティとともにユーザーに再表示される可能性があります。このようなケース(onResume() コールバックまたは onPause() コールバックを取得する下位のアクティビティなど)は、状況に応じて適切に処理する必要があります。また、ユーザーがアクティビティを操作する可能性もあります。たとえば、動画リストのアクティビティを表示状態にして、動画再生のアクティビティを PIP モードにした場合は、ユーザーがリストから新しい動画を選択したら、それに応じて PIP アクティビティを更新する必要があります。

他のサンプルコード

Android で作成されたサンプルアプリをダウンロードするには、Android PictureInPicture Sample をご覧ください。Kotlin で記述されたサンプルアプリをダウンロードするには、Android PictureInPicture Sample (Kotlin) をご覧ください。