はじめに
こんにちは、Androidアプリ開発グループの休井です。
私が担当しているアプリで、MV再生機能とピクチャーインピクチャーの機能を追加することになりました。
動画再生については弊社のいくつかのアプリで実装されていますが、ピクチャーインピクチャーについては今回が初の導入となります。
そこで、今回はピクチャーインピクチャーの実装方法やカスタマイズ方法についてご紹介します。
ピクチャーインピクチャーの概要
Picture in Picture(PiP)モードは、ユーザーが他のアプリを操作しながらビデオを小さなウィンドウで再生できる機能です。
主にビデオ再生アプリで使用されることが多く、ユーザーエクスペリエンスを向上させるための重要な機能です。
前提
以下の依存関係がプロジェクトに追加されていること。
実装方法
- 動画を再生する
まずは、動画を再生する部分の実装をします。
今回はPiPの実装が目的なので、ローカルに持つMP4ファイルを再生するだけのシンプルな実装にします。
こちらを実行すると以下のようになり、動画が再生できるようになります。// MainActivity.kt private lateinit var exoPlayer: ExoPlayer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initializePlayer() setContent { MaterialTheme { VideoPlayer(exoPlayer) } } } private fun initializePlayer() { // ExoPlayerの初期化 exoPlayer = ExoPlayer.Builder(this).build().apply { // MediaItemの作成 val mediaItem = MediaItem.fromUri("android.resource://${packageName}/${R.raw.video}") setMediaItem(mediaItem) // 再生準備開始 prepare() } } @Composable fun VideoPlayer(player: ExoPlayer) { AndroidView(factory = { // PlayerViewに作成したExoPlayerをセット PlayerView(it).apply { this.player = player } }) }
AndroidManifest.xmlの設定
次に、アプリがPiPモードをサポートできるようにします。
AndroidManifest.xmlファイルで、動画再生を行うActivityに対してandroid:supportsPictureInPicture="true"の宣言を追加します。<activity> android:name=".MainActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:supportsPictureInPicture="true" > ... </activity>- PiPモードの切り替え
onUserLeaveHint()メソッドをオーバーライドして、ユーザーがアクティビティを離れるときにPiPモードに切り替わるようにします。override fun onUserLeaveHint() { enterPictureInPictureMode( PictureInPictureParams.Builder() .setAspectRatio(Rational(16, 9)) .build() ) super.onUserLeaveHint() }enterPictureInPictureMode()がPiPモードに切り替わるためのメソッドです。
ここでは、PiPウィンドウのアスペクト比が16:9で表示されるよう設定しました。
上記処理を追加することで、端末のホームを表示しようとしたときにPiPモードに切り替わるようになりました。
UIの調整
PiPモードに切り替わるようになりましたが、PiPで表示したときにUIの崩れが起きてしまいました。
PiPに切り替わった時に、PiPのウィンドウいっぱいに動画が表示されるよう調整します。
まず、デフォルトで表示されているアクションバーを非表示にしたいのでtheme.xmlを以下のようにします。
これを<?xml version="1.0" encoding="utf-8"?> <resources> <style name="Theme.PictureInPictureSample" parent="android:Theme.Material.Light.NoActionBar" /> </resources>AndroidManifest.xmlのActivityの宣言に追加します。
これで、アクションバーが非表示になりました。<activity> android:name=".MainActivity" android:theme="@style/Theme.PictureInPictureSample" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:supportsPictureInPicture="true" > ... </activity> 次に、PiPの切り替えでアクションバーの表示/非表示が行われるようにします。
Activityのメンバ変数としてPiPのStateを持ち、onPictureInPictureModeChanged()をオーバーライドしてStateの書き換えを行います。
これを使用して、アクションバーの表示制御を行います。private var isInPiPModeState = mutableStateOf(false) override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) isInPiPModeState.value = isInPictureInPictureMode }
これにより、PiPモードに切り替わった時に動画のみが表示されるようになりました。override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) initializePlayer() setContent { val isInPiPMode by isInPiPModeState MaterialTheme { VideoPlayer( player = exoPlayer, isInPiPMode = isInPiPMode ) } } } @kotlin.OptIn(ExperimentalMaterial3Api::class) @Composable fun VideoPlayer(player: Player, isInPiPMode: Boolean) { Scaffold( topBar = { if (!isInPiPMode) { TopAppBar( title = { Text(text = "PictureInPictureSample") } ) } } ) { innerPadding -> AndroidView( factory = { PlayerView(it).apply { this.player = player playerView = this } }, modifier = Modifier .fillMaxSize() .padding(innerPadding) ) } }
カスタムアクションの設定
AndroidのPiPには、標準動作として設定画面への遷移、フル画面表示、閉じる(D&D又はボタン押下)が備わっています。
アクティブなMediaSessionが存在する場合は、それに加え動画の再生/停止、次へ、前へボタンが表示されるようです。
これらの標準動作については、現在の仕様では動作を変えたりボタンを消したりという変更は難しそうでしたが、PiPのウィンドウに追加で自前のボタンを設置することは可能です。
今回は、お気に入りボタンを設置してみたいと思います。 まずは、お気に入りアクション検知用の定数と、BroadCastReceiverを定義します。
作成したBroadCastReceiverをManifestに追加します。companion object { const val ACTION_FAVORITE = "favorite" } class PiPActionReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == ACTION_FAVORITE) { Toast.makeText(context, "お気に入りに登録したよ!", Toast.LENGTH_SHORT).show() } } }
次に、RemoteActionを作成し、PiP起動時にパラメーターにセットします。<receiver android:name=".MainActivity$PiPActionReceiver" />
これで、PiPのウィンドウにお気に入りボタンが表示され、押下時にトーストを出すことができました。private fun createRemoteAction( @DrawableRes iconRes: Int, requestCode: Int, actionIntent: Intent, title: String, description: String ): RemoteAction { val pendingIntent = PendingIntent.getBroadcast( this, requestCode, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val icon = Icon.createWithResource(this, iconRes) return RemoteAction(icon, title, description, pendingIntent) } override fun onUserLeaveHint() { val favoriteAction = createRemoteAction( R.drawable.ic_favorite, 1, Intent(this, PiPActionReceiver::class.java).apply { action = ACTION_FAVORITE }, "Favorite", "Add to favorite" ) val params = PictureInPictureParams.Builder() .setAspectRatio(Rational(16, 9)) .setActions(listOf(favoriteAction)) .build() enterPictureInPictureMode(params) super.onUserLeaveHint() }
まとめ
今回の実装では、動画再生、PiPモードへの切り替え、UIの調整、カスタムアクションの追加といった一連の手順を紹介しました。
導入ということで機能は単純なものを扱いましたが、実際にプロダクトで実装する時はアプリに合わせた追加機能やユーザーがより便利にアプリを使えるよう導線や動作を調整していきたいと思います。
最後までお読みいただきありがとうございました。
chiaki.kyui