ピクチャー イン ピクチャー(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 モードにできます。ユーザーが別のアクティビティを同時に実行している間も、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 モードに入るか終了すると、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 を作成してカスタム アクションを明示的に指定し、PIP モードに入るときに enterPictureInPictureMode(android.app.PictureInPictureParams) または setPictureInPictureParams(android.app.PictureInPictureParams) を使用してパラメータを渡すこともできます。ここで注意が必要なのは、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)をご覧ください。