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:supportsPictureInPicture
を true
に設定して、動画アクティビティをマニフェストに登録します。また、PIP モードの遷移中にレイアウト変更が発生しても、アクティビティが再起動しないように、アクティビティがレイアウト構成の変更を処理するように指定します。
<activity android:name="VideoActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
アクティビティを PIP に切り替える
Android 12 以降では、setAutoEnterEnabled
フラグを true
に設定することで、アクティビティを PIP モードに切り替えることができます。この設定により、アクティビティは必要に応じて自動的に PIP モードに切り替わります。onUserLeaveHint
で enterPictureInPictureMode()
を明示的に呼び出す必要はありません。また、画面遷移をスムーズに行えるというメリットもあります。詳しくは、ジェスチャー ナビゲーションから 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 モードで動画コンテンツに移行する際(全画面表示からホーム画面にスワイプする場合など)が非常にスムーズになります。
この変更を行うには、次の手順を完了します。詳細については、こちらのサンプルをご覧ください。
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());
最新の
PictureInPictureParams
を使って、早い段階でsetPictureInPictureParams
を呼び出します。アプリはonUserLeaveHint
コールバックを待機しません(Android 11 では待機しません)。たとえば、最初の再生と後続の再生で、アスペクト比が変更された場合に
setPictureInPictureParams
を呼び出すことができます。必要な場合にのみ
setAutoEnterEnabled(false)
を呼び出します。たとえば、現在の再生が一時停止状態の場合、PIP に入ることはおすすめしません。
PIP モードの開始と終了のために適切な sourceRectHint
を設定する
Android 8.0 での PiP の導入以降、setSourceRectHint
は、ピクチャー イン ピクチャーへの移行後に表示されるアクティビティの領域(動画プレーヤーの動画ビューの境界など)を示していました。
Android 12 では、システムは sourceRectHint
を使用して、PIP モードの開始時と終了時の両方で、よりスムーズなアニメーションを実装します。
PIP モードの開始と終了のために sourceRectHint
を適切に設定するには:
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);
必要に応じて、システムが終了遷移を開始する前に
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:launchMode
を singleTask
に設定します。
<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)をご覧ください。