ピクチャー イン ピクチャー(PIP)を使って動画を追加する

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

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

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

PIP は、Android 14(API レベル 34)以降を搭載した互換性のある Android TV OS デバイスでもサポートされています。多くの類似点がありますが、TV で PIP を使用する場合は追加の考慮事項があります。

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

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

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

  • ウィンドウをダブルタップして、現在の PIP サイズと、最大または最小の PIP サイズを切り替えます。たとえば、最大化したウィンドウをダブルタップすると最小化され、その逆も同様です。

  • ウィンドウを左端または右端にドラッグすると、ウィンドウが閉じます。ウィンドウを再表示するには、閉じたウィンドウの表示部分をタップするか、ウィンドウをドラッグします。

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

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

  • ユーザーがホームボタンをタップするか、ホームまでスワイプすると、アクティビティは PIP モードになります。このようにして、ユーザーが別のアクティビティを同時に実行している間、Google マップはルートを引き続き表示します。

  • ユーザーが動画から他のコンテンツをブラウジングするために動画から PIP モードに移動すると、

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

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

PIP サポートを宣言する

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

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

アクティビティを PIP に切り替える

Android 12 以降では、setAutoEnterEnabled フラグを true に設定することで、アクティビティを PIP モードに切り替えることができます。この設定により、アクティビティは onUserLeaveHintenterPictureInPictureMode() を明示的に呼び出すことなく、必要に応じて自動的に PIP モードに切り替わります。さらに 非常にスムーズな遷移が可能になるという 利点もあります詳しくは、ジェスチャー ナビゲーションから PIP モードへの遷移をスムーズにするをご覧ください。

Android 11 以前をターゲットとしている場合、アクティビティで enterPictureInPictureMode() を呼び出して PIP モードに切り替える必要があります。たとえば、次のコードは、ユーザーがアプリ 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 では、全画面ウィンドウと PIP ウィンドウ間のアニメーション遷移の外観が大幅に改善されました。該当する変更をすべて実装することを強くおすすめします。一度実装すると、これらの変更は、折りたたみ式デバイスやタブレットなどの大画面に合わせて自動的にスケーリングされます。追加の作業は必要ありません。

アプリに該当するアップデートが含まれていない場合、PIP 遷移は引き続き機能しますが、アニメーションの洗練度は低くなります。たとえば、全画面表示から PIP モードに移行すると、遷移中に PIP ウィンドウが消え、遷移が完了すると再表示されます。

これらの変更には、次のものが含まれます。

  • ジェスチャー ナビゲーションから PIP モードへの移行をよりスムーズに
  • PIP モードの開始と終了のための適切な sourceRectHint の設定
  • 動画以外のコンテンツのシームレスなサイズ変更を無効にする

洗練された遷移エクスペリエンスを実現するための参考として、Android Kotlin の PictureInPicture サンプルをご覧ください。

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

Android 12 以降では、setAutoEnterEnabled フラグにより、ジェスチャー ナビゲーションを使用して PIP モードで動画コンテンツに遷移するアニメーションがよりスムーズに表示されます(全画面表示から上にスワイプしてホーム画面に移動する場合など)。

この変更を行うには、次の操作を行います。こちらのサンプルを参考にしてください。

  1. setAutoEnterEnabled を使用して PictureInPictureParams.Builder を作成します。

    Kotlin

    setPictureInPictureParams(PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build())
    

    Java

    setPictureInPictureParams(new PictureInPictureParams.Builder()
        .setAspectRatio(aspectRatio)
        .setSourceRectHint(sourceRectHint)
        .setAutoEnterEnabled(true)
        .build());
    
  2. 最新の PictureInPictureParams を使って、早い段階で setPictureInPictureParams を呼び出します。アプリは onUserLeaveHint コールバックを待機しません(Android 11 の場合と同様です)。

    たとえば、最初の再生で setPictureInPictureParams を呼び出し、アスペクト比が変更された場合は後続の再生で呼び出すことができます。

  3. setAutoEnterEnabled(false) を呼び出しますが、必要な場合のみ呼び出します。たとえば、現在の再生が一時停止状態の場合、PIP に切り替える必要はありません。

PIP モードの開始と終了のための適切な sourceRectHint を設定

Android 8.0 での PIP の導入以降、setSourceRectHint は、ピクチャー イン ピクチャーへの移行後に表示されるアクティビティの領域(動画プレーヤーの動画ビューの境界など)を示していました。

Android 12 では、sourceRectHint を使用して、PIP モードの開始時と終了時の両方で、非常にスムーズなアニメーションが実装されます。

PIP モードの開始と終了について sourceRectHint を適切に設定するには:

  1. 適切な境界を sourceRectHint として使用して PictureInPictureParams を作成します。動画プレーヤーには、レイアウト変更リスナーをアタッチすることをおすすめします。

    Kotlin

    val mOnLayoutChangeListener =
    OnLayoutChangeListener { v: View?, oldLeft: Int,
            oldTop: Int, oldRight: Int, oldBottom: Int, newLeft: Int, newTop:
            Int, newRight: Int, newBottom: Int ->
        val sourceRectHint = Rect()
        mYourVideoView.getGlobalVisibleRect(sourceRectHint)
        val builder = PictureInPictureParams.Builder()
            .setSourceRectHint(sourceRectHint)
        setPictureInPictureParams(builder.build())
    }
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener)
    

    Java

    private final View.OnLayoutChangeListener mOnLayoutChangeListener =
            (v, oldLeft, oldTop, oldRight, oldBottom, newLeft, newTop, newRight,
            newBottom) -> {
        final Rect sourceRectHint = new Rect();
        mYourVideoView.getGlobalVisibleRect(sourceRectHint);
        final PictureInPictureParams.Builder builder =
            new PictureInPictureParams.Builder()
                .setSourceRectHint(sourceRectHint);
        setPictureInPictureParams(builder.build());
    };
    
    mYourVideoView.addOnLayoutChangeListener(mOnLayoutChangeListener);
    
  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());
        }
    });
    
    

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

Android 12 では setSeamlessResizeEnabled フラグが追加されています。これにより、PIP ウィンドウで動画以外のコンテンツのサイズを変更する際に、よりスムーズなクロスフェード アニメーションが実現します。以前は、PIP ウィンドウで動画以外のコンテンツのサイズを変更すると、視覚的なアーティファクトが表示されていました。

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

Kotlin

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

Java

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

PIP 中の UI を処理する

アクティビティが PIP モードに入るか PIP モードを終了すると、システムは 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 PiP 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 PiP mode.
    } else {
        // Restore the full-screen UI.
        ...
    }
}

コントロールを追加する

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

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

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

PIP の状態で動画再生を続行する

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

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

setAutoEnterEnabled フラグを true に設定しておらず、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() メソッドを呼び出します。

PIP に対して単一の再生アクティビティを使用する

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

動画再生リクエストに対して単一のアクティビティが使用され、必要に応じて 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 モードに切り替えるときは、動画以外のコンテンツを表示しないようにしてください。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 サンプル(Kotlin)をご覧ください。