アプリケーション層から ApsaraVideo Player ソフトウェア開発キット (SDK) を呼び出し、システム API を使用することで、小ウィンドウ再生を実装できます。このトピックでは、小ウィンドウ再生の実装方法、ソリューションの推奨事項、および Android と iOS の技術的な実装について詳しく説明します。
概要
モバイルアプリケーションにおいて、小ウィンドウ再生は、ビデオプレーヤーが画面上にフローティングする小さなウィンドウに表示される機能です。これにより、ユーザーは他のコンテンツを閲覧しながらビデオの視聴を続けることができます。この機能は、E コマースのライブストリーミング、ショートビデオ、短編ドラマ、オンライン教育などのシナリオで一般的であり、より便利なマルチタスキング体験を提供します。
小ウィンドウ再生を実装するには、主に 2 つの方法があります。
ピクチャーインピクチャー (PiP)
フローティングウィンドウ
これら 2 つの方法は、技術的な実装、プラットフォームのサポート、権限リクエスト、およびユーザーインタラクションの点で異なります。ビジネスシナリオに最も適した方法を選択する必要があります。
さらに、小ウィンドウ再生機能は短編ドラマソリューションと深く統合されています。エピソードの自動切り替え機能と連携して、シナリオベースで再利用可能なエンドツーエンドのソリューションを構築します。詳細については、「短編ドラマソリューション」をご参照ください。
小ウィンドウ再生ソリューション
ピクチャーインピクチャー (PiP)
定義
これは、オペレーティングシステムによってネイティブにサポートされている小ウィンドウ再生方法です。プレーヤーは、通常は画面の隅に固定されるフローティングウィンドウに縮小されます。これにより、ユーザーは他のアプリケーションやページを同時に閲覧できます。
特徴
システムのサポートに応じて、小ウィンドウはホーム画面や他のアプリケーションの上にフローティングできます。
通常、ウィンドウは自由にドラッグしたりサイズ変更したりすることはできません。システムがインタラクションを制御します。
Android 8.0+ や iOS 14+ など、ネイティブのオペレーティングシステムの機能に依存します。
シナリオ
このソリューションは、高い互換性が必要で、アプリのレビューに合格する必要があるビジネスシナリオや、システムの機能を最大限に活用したいシナリオに適しています。
実装リファレンス
ApsaraVideo Player API-Example サンプルプロジェクトの PiP 実装を参照し、ビジネス要件に合わせて調整することをお勧めします。詳細については、API-Example-Android および API-Example-iOS プロジェクトの
PictureInPictureモジュールをご参照ください。
フローティングウィンドウ
定義
これは、アプリケーションによって実装されるカスタムの小ウィンドウ再生ソリューションです。プレーヤーは、アプリケーションのインターフェイスや他のアプリケーション上で自由にドラッグおよびサイズ変更できる小ウィンドウに表示されます。
特徴
ドラッグやスケーリングなど、カスタムのウィンドウスタイルとインタラクションをサポートします。
Android では特殊なアクセス許可が必要であり、一部のシステムやデバイスでは制限が課される場合があります。
Apple の App Review によって拒否されるリスクがあるため、iOS プラットフォームでは推奨されません。
シナリオ
このソリューションは主に Android 向けであり、高度にカスタマイズされた小ウィンドウインタラクションが必要なシナリオや、ネイティブのピクチャーインピクチャー (PiP) がサポートされていない場合の代替手段として適しています。
実装リファレンス
ApsaraVideo Player API-Example プロジェクトのフローティングウィンドウ実装を参照し、ビジネスニーズに合わせて調整することをお勧めします。詳細については、API-Example-Android および API-Example-iOS プロジェクトの
FloatWindowモジュールをご参照ください。
ソリューションの推奨事項
プラットフォームがネイティブ PiP をサポートしている場合は、PiP ソリューションを使用することをお勧めします。開発が簡素化され、互換性が向上し、アプリレビューに合格する可能性が高まります。
カスタムの小ウィンドウ体験が必要な場合や、ネイティブ PiP が利用できない場合は、フローティングウィンドウソリューションを検討してください。ただし、権限リクエストとプラットフォームのコンプライアンスリスクに注意してください。
iOS プラットフォームでは、公式の PiP 機能を使用してください。カスタムのフローティングウィンドウソリューションは推奨しません。
このトピックで説明されているすべての機能のコードと実装の詳細については、API-Example サンプルプロジェクトを参照し、ベストプラクティスに基づいてそれを適応および拡張することをお勧めします。具体的な実装の詳細については、以下の技術ソリューションと、対応する API-Example-Android および API-Example-iOS のソースコードをご参照ください。
Android の技術ソリューション
Android システムは、フローティングウィンドウや PiP ソリューションなど、いくつかのソリューションを提供しています。一般的な例として、E コマースシナリオで使用されるフローティングウィンドウソリューションがあります。このセクションでは、最も一般的な実装方法のみを説明します。
フローティングウィンドウ
フローティングウィンドウは、他のアプリケーションの上に表示できる Android システムのウィンドウの一種です。自由にドラッグ、サイズ変更、閉じることができます。リマインダー、通知、広告によく使用されます。
Android システムでは、各ウィンドウは Window オブジェクトに対応します。フローティングウィンドウは特殊なタイプの Window です。通常、フローティングウィンドウは View システムの `PopupWindow` を使用して実装できます。
`PopupWindow` は `WindowManager.LayoutParams` を継承し、アニメーション機能を持つクラスです。つまり、その位置、サイズ、表示スタイルを制御できます。また、`PopupWindow` を使用して、他のアプリケーションに影響を与えたり、Activity の遷移を必要とせずにカスタムのフローティングウィンドウを実装することもできます。
次のステップに従ってください:
`AndroidManifest.xml` ファイルでフローティングウィンドウの権限を宣言します。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />フローティングウィンドウを表示する必要がある Activity または Service で、`WindowManager` と `PopupWindow` を作成します。次に、表示位置、サイズ、コンテンツなどのプロパティを設定します。
// レイアウトを作成します。 View layout = View.inflate(this, R.layout.float_window, null); // マネージャーを作成します。 WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE); // フローティングウィンドウを作成します。 PopupWindow mPopupWindow = new PopupWindow(layout, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); // 表示位置を設定します。 mPopupWindow.showAtLocation(parentView, Gravity.LEFT | Gravity.TOP, x, y); // クリック可能かつフォーカス可能にします。 mPopupWindow.setTouchable(true); mPopupWindow.setFocusable(true); // 背景色を設定します。 mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));フローティングウィンドウレイアウト内のコントロールのクリックイベントとドラッグリスナーを設定します。
mPopupWindow.setOnTouchListener(new View.OnTouchListener() { int lastX, lastY; int paramX, paramY; @Override public boolean onTouch(View v, MotionEvent event) { // 画面に対する現在のタッチポイントの座標を取得します。 int x = (int) event.getRawX(); int y = (int) event.getRawY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; paramX = mPopupWindow.getLayoutParams().x; paramY = mPopupWindow.getLayoutParams().y; break; case MotionEvent.ACTION_MOVE: int dx = x - lastX; int dy = y - lastY; mPopupWindow.update(paramX + dx, paramY + dy, -1, -1); break; } return false; } });重要Android 6.0 以降では、フローティングウィンドウの権限を動的にリクエストする必要があります。ただし、この機能は一部の Android 8.0 デバイスではまだサポートされていません。
ピクチャーインピクチャー
Android 8.0 (API レベル 26) 以降、Android は PiP モードで Activity を起動する機能を導入しました。PiP は、ビデオ再生に最もよく使用される特殊なタイプのマルチウィンドウモードです。このモードでは、ユーザーはアプリ間を移動したり、ホーム画面でコンテンツを閲覧したりしながら、ビデオを画面の隅の小さなウィンドウに固定できます。
PiP は、Android 7.0 で利用可能なマルチウィンドウモード API を使用して、固定のビデオオーバーレイウィンドウを作成します。アプリケーションに PiP を追加するには、PiP をサポートする Activity を登録し、必要に応じて Activity を PiP モードに切り替え、Activity が PiP モードのときに UI 要素が非表示になり、ビデオ再生が継続されることを確認する必要があります。
PiP ウィンドウは、他のすべてのウィンドウの上に表示され、通常はシステムによって選択された隅に表示されます。
詳細については、「Android · ピクチャーインピクチャー (PiP) のサポート」をご参照ください。
次のステップに従ってください:
`AndroidManifest.xml` ファイルで Activity の PiP サポートを宣言します。
<activity android:name="VideoActivity" android:supportsPictureInPicture="true" android:configChanges= "screenSize|smallestScreenSize|screenLayout|orientation" ... />デフォルトでは、Android システムはアプリケーションに PiP サポートを自動的に提供しません。アプリケーションで PiP をサポートする場合は、`AndroidManifest.xml` マニフェストファイルにビデオ Activity を登録し、`android:supportsPictureInPicture` 属性を `true` に設定します。
PiP をサポートする Activity を登録する際には、Activity がレイアウト構成の変更を処理することも指定する必要があります。これにより、PiP モードの遷移中にレイアウトの変更が発生した場合に Activity が再起動するのを防ぎ、よりスムーズなユーザーエクスペリエンスを提供します。
重要RAM の少ないデバイスでは、PiP モードを使用できない場合があります。アプリケーションで PiP を使用する前に、`hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)` を呼び出して可用性を確認してください。
Activity を PiP モードに切り替えます。
PiP モードに入る最も一般的な方法は次のとおりです。
ボタンによってトリガーされる (例: ボタンをタップする)
`onClicked(View)`、`onOptionsItemSelected(MenuItem)` など。
@Override public void onActionClicked(Action action) { if (action.getId() == R.id.lb_control_picture_in_picture) { // ボタンからトリガーされます。 enterPictureInPictureMode(); return; } ... }アプリケーションを離れることによってトリガーされる (例: ホームボタンを押す)
`onUserLeaveHint()`
PiP モードに入るには、Activity が `enterPictureInPictureMode()` を呼び出す必要があります。`onUserLeaveHint()` をオーバーライドすることで、ユーザーがホームボタンまたは最近使ったアプリボタンを押したときに、アプリケーションが自動的に PiP モードに切り替わるようにすることができます。
@Override public void onUserLeaveHint () { // アプリケーションを意図的に離れることによってトリガーされます。 if (iWantToBeInPipModeNow()) { enterPictureInPictureMode(); } }戻る操作によってトリガーされる (例: 戻るボタンを押す)
`onBackPressed()`
@Override public void onBackPressed() { super.onBackPressed(); // 戻る操作からトリガーされます。 enterPictureInPictureMode(); }
PiPモードで UI 要素を処理します。
Activity が PiP モードに出入りすると、システムは `Activity.onPictureInPictureModeChanged()` メソッドまたは `Fragment.onPictureInPictureModeChanged()` メソッドを呼び出します。
これらのコールバックをオーバーライドして、Activity の UI 要素を再描画する必要があります。PiP モードでは、Activity は小さなウィンドウとして表示されることに注意してください。ユーザーはアプリケーションの UI 要素と対話できず、小さなウィンドウの詳細を見るのが難しい場合があります。最小限の UI を備えたビデオ再生 Activity が、最高のユーザーエクスペリエンスを提供します。
PiP モードを終了するときのよりスムーズなアニメーションをサポートします。
Activity が PiP モードを終了するときに、UI 要素に終了アニメーションを追加して、トランジションの滑らかさと視覚効果を向上させることができます。
コントロールを追加します。
PiP モードでは、ユーザーはアプリケーションの UI 要素と対話できない場合があります。そのため、PiP ウィンドウに簡単なコントロールを追加して、ユーザーが再生/一時停止や次/前のエピソードなどの基本操作を実行できるようにすることを検討できます。
ビデオ以外のコンテンツのシームレスなサイズ変更を無効にします。
PiP モードでは、ビデオコンテンツは通常、適切な縦横比を維持し、引き伸ばされたりトリミングされたりしないようにする必要があります。ただし、テキストや画像などのビデオ以外のコンテンツについては、小さなウィンドウでコンテンツが読めなくなったり歪んだりするのを防ぐために、シームレスなサイズ変更を無効にする必要がある場合があります。シームレスなサイズ変更を無効にすることで、ビデオ以外のコンテンツが PiP モードで表示され、使用可能な状態を維持できます。
PiP モードでビデオの再生を続行します。
Activity が PiP モードに切り替わると、システムは Activity を一時停止状態にし、Activity の `onPause()` メソッドを呼び出します。ただし、ビデオ再生は PiP モードで続行されるべきであり、一時停止すべきではありません。
Android 7.0 以降:
システムが Activity の `onStop()` メソッドを呼び出すときに、ビデオ再生を一時停止する必要があります。
システムが Activity の `onStart()` メソッドを呼び出すときに、ビデオ再生を再開する必要があります。
これにより、`onPause()` メソッドでアプリケーションが PiP モードであるかどうかを確認する必要がなくなり、ビデオの再生を続行できます。
`onPause()` の実装でビデオ再生を一時停止する必要がある場合は、`isInPictureInPictureMode()` を呼び出してアプリケーションが PiP モードであるかどうかを確認し、それに応じて再生状態を処理します。次のコードは例です。
@Override public void onPause() { // PiP モード中に呼び出された場合、再生を一時停止しません。 if (isInPictureInPictureMode()) { // 再生を続行します。 ... } else { // 一時停止したアクティビティの動作には、既存の再生ロジックを使用します。 ... } }
iOS の技術ソリューション
現在、PiP 機能を実装するには 4 つの方法があります。
共有 `AVSampleBufferDisplayLayer` とグローバルフレーム配信に基づく PiP ソリューション (推奨)
短編ドラマなどの複雑なシナリオで PiP とエピソードの自動切り替えを実装するには、`AVSampleBufferDisplayLayer` を使用してビデオフレームを受信し、PiP ページに表示できます。
説明詳細なコード例については、PipDisplayLayer の Picture-in-Picture (SampleBufferDisplayLayer) モジュールをご参照ください。このプロジェクトは、iOS 向け ApsaraVideo Player SDK の Objective-C サンプルプロジェクトです。開発者が共有 `AVSampleBufferDisplayLayer` とグローバルフレーム配信に基づく PiP ソリューションを迅速に学習して使用できるように設計されています。
組み込みの `WKWebView`
アプリケーションでビデオ再生に `WKWebView` を使用する場合、すでに組み込みの PiP 機能があります。
`AVPlayerViewController` の使用
プレーヤーに高い要件がない場合は、`AVPlayerViewController` を直接使用できます。すでに PiP 機能が提供されています。`allowsPictureInPicturePlayback` プロパティを `YES` に設定するだけで、プレーヤーインターフェイスに PiP ボタンが表示されます。
`AVPictureInPictureController` でラップされたカスタムプレーヤー
カスタムプレーヤーを使用して PiP 機能を有効にしたい場合は、プレーヤーを `AVPictureInPictureController` でラップできます。これにより、PiP を簡単に実装でき、`AVPictureInPictureController` には組み込みのアニメーション効果があります。PiP ボタンは自分で実装する必要があることに注意してください。システムは、PiP アイコンを使用するための関連 API (`pictureInPictureButtonStartImage`) を提供しています。
フローティングウィンドウ
簡単に言うと、`UIWindow` タイプの新しいウィンドウを作成し、そのウィンドウにビデオプレーヤーのビューを追加し、ジェスチャーでウィンドウの位置とサイズを制御して、フローティング PiP 効果を実現できます。
Apple のデザインガイドラインでは、アプリケーションでのフローティングウィンドウの使用を明示的に禁止しています。フローティングウィンドウを使用すると、レビュー中にアプリケーションが拒否されたり、上場廃止になったりする可能性があります。また、ユーザーエクスペリエンスとプライバシー保護の問題も考慮する必要があります。したがって、フローティングウィンドウは慎重に使用してください。
ピクチャーインピクチャー
PiP 機能は iOS 9 で導入されましたが、以前のバージョンでは iPad でのみ利用可能でした。iPhone ユーザーが PiP 機能を使用できるようになったのは iOS 14 からです。
iOS での PiP の実装ソリューションは 2 つあります。
iOS 14 のソリューション
iOS 14 では、システム提供の `AVPlayer` で `AVPictureInPictureController` を初期化して、アプリケーションがバックグラウンドに送られたり、セカンダリページに移動したりしたときにピクチャーインピクチャー (PiP) 効果を有効にすることができます。このソリューションは、プレーヤーの要件がそれほど厳しくないシナリオに適しています。
iOS 15 以降では新しいソリューションが利用可能です。
iOS 15 以降では、AVSampleBufferDisplayLayer を使用して AVPictureInPictureController を作成し、シームレスな PiP 再生を実現できます。この方法は、カスタムプレーヤーで PiP 機能を実装するためによく使用されます。ApsaraVideo Player SDK には PiP 機能が含まれるようになりました。詳細については、「ピクチャーインピクチャー (PiP)」をご参照ください。
次のステップに従ってください:
iOS 15 の実装は iOS 14 の実装と似ています。このトピックでは、iOS 14 のソリューションを例として使用します。
バックグラウンドモードを有効にします。

フレームワーク `#import <AVKit/AVKit.h>` をインポートし、`AVPictureInPictureController` を作成します。
// 1. PiP がサポートされているか確認します。 if ([AVPictureInPictureController isPictureInPictureSupported]) { // 2. 権限を有効にします。 @try { NSError *error = nil; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionOrientationBack error:&error]; [[AVAudioSession sharedInstance] setActive:YES error:&error]; } @catch (NSException *exception) { NSLog(@"AVAudioSession error occurred"); } self.pipVC = [[AVPictureInPictureController alloc] initWithPlayerLayer:self.player]; self.pipVC.delegate = self; }PiP を開始または停止します。
if (self.pipVC.isPictureInPictureActive) { [self.pipVC stopPictureInPicture]; } else { [self.pipVC startPictureInPicture]; }`AVPictureInPictureControllerDelegate` を設定します。
// PiP が開始されようとしています。 - (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController; // PiP が開始されました。 - (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController; // PiP の開始に失敗しました。 - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error; // PiP が停止されようとしています。 - (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController; // PiP が停止しました。 - (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController; // PiP が閉じており、再生インターフェイスが復元されています。 - (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL restored))completionHandler;重要グローバル変数を使用して PiP コントローラーを保持します。`pictureInPictureControllerWillStartPictureInPicture` でそれを保持し、`pictureInPictureControllerDidStopPictureInPicture` で解放できます。
PiP ボタンをタップする以外の方法で PiP コントローラーが開かれることがあります。`viewWillAppear` メソッドでそれを確認して閉じることができます。
PiP ウィンドウがすでにアクティブな状態で新しいウィンドウを開始したい場合は、現在のウィンドウが完全に閉じるのを待ってから新しいウィンドウを開始してください。PiP ウィンドウを閉じるには時間がかかるため、これにより未知のエラーを防ぐことができます。
`AVPictureInPictureController` を作成して同時に PiP を開始しようとすると、失敗することがあります。この場合、PiP の開始を遅延させてみてください。