Picture in Picture Mode in android. Show video in a mini-window

Main photo (replace with your own)

Main photo (replace with your own)

Preparation

I will skip setting up the video itself so as not to load the article with unnecessary information.
An example of a customized video application can be cloned from here (branch video_player_added)

Let's start with this

Let’s start with this

Restrictions

Picture in Picture (PiP) mode appeared in android 8.0 (api level 26). You can check the version like this:

(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)

On devices with a small amount of RAM PiP mode may also be unavailable. Check Availability PiP mode it is possible like this:

context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)

activity setting

Let’s make changes to AndroidManifest.xml

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

android:supportsPictureInPicture – flag indicating that the activity supports PiP-mode
android:configChanges – a list of types of configuration changes that activity handles itself. Default, activity recreated when a configuration change occurs. If indicated configChangesthen activity just call the method Activity.onConfigurationChanged() and it won’t recreate.

Switching to PiP-mode

Transition activity in PiP-mode occurs through a method call Activity.enterPictureInPictureMode(PictureInPictureParams params)

There are two options for switching to PiP-mode:

enterPipButton.setOnClickListener {
    enterPictureInPictureMode(params)
}
  • Call enterPictureInPictureMode() inside Activity.onUserLeaveHint()
    onUserLeaveHint() – the method that is called when activity goes into the background only due to user actions.
    For example, if the user presses the Home button, then onUserLeaveHint() will be called. But if our activity will block the incoming call screen, then onUserLeaveHint() will not be called.

override fun onUserLeaveHint() {
    super.onUserLeaveHint()

    enterPictureInPictureMode(params)
}

Also starting from android 12 (api level 31) you can set the flag PictureInPictureParams.setAutoEnterEnabledwhich will automatically put the activity in PiP-mode when it is minimized.

PiP-mode parameters

PiP-mode is configured via PictureInPictureParams.
Here are the main parameters:

aspect ratio examples

aspect ratio examples

  • setSourceRectHint(sourceRectHint) – bounds of the content that will be visible during the transition to PiP-mode. For best effect sourceRectHint must match aspectRatio.
    Notice! When the transition to PiP-mode is completed, the content borders will be recalculated from the top border activity according to aspectRatio.

  • setAutoEnterEnabled(true) – the flag described above, which says that when the activity is minimized, it needs to be shown in PiP-mode. Available from android 12 (api level 31)

  • setActions(actions) – adds activity interaction buttons to PiP-mode. More details will be described below.

UI processing in PiP-mode

When activity switched to PiP-mode, it is no longer possible to interact with it. In fact activity goes into state onPause(). Therefore, we need to hide all unnecessary buttons, controls and just small elements (they will still not be visible)

To understand that activity switched to PiP-mode is possible inside the methodActivity.onPictureInPictureModeChanged().

override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean,
                                           newConfig: Configuration) {
    if (isInPictureInPictureMode) {
        // Скрываем лишние кнопки, мелкие элементы итд
    } else {
        // Восстанавлиаем состояние ui
    }
}

Interaction with activity in PiP-mode

To interact with activity we need to add RemoteAction.

val icon = Icon.createWithResource(context, R.drawable.play)
val intent = PiPModeActionsReceiver.createPlayIntent(this)

//Не буду останавливаться на теме PendingIntent, чтобы не перегружать статью
val pendingIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_IMMUTABLE)

val action = RemoteAction(
    icon,
    "Play",
    "Play video",
    pendingIntent
)

Receive click events RemoteAction we’ll be through broadcast receiver.
Let’s write its implementation:

class PiPModeActionsReceiver(
    private val pipModeActionsListener: PiPModeActionsListener
) : BroadcastReceiver() {

    companion object {
        private const val ACTION = "pip_mode_action"
        private const val EXTRA_CONTROL_TYPE = "control_type"
        private const val REQUEST_PLAY = 1
        private const val REQUEST_PAUSE = 2

        fun createPlayIntent(context: Context): Intent {
            val intent = Intent(context, PiPModeActionsReceiver::class.java)
            intent.putExtra(EXTRA_CONTROL_TYPE, REQUEST_PLAY)
            return intent
        }

        fun createPauseIntent(context: Context): Intent {
            val intent = Intent(context, PiPModeActionsReceiver::class.java)
            intent.putExtra(EXTRA_CONTROL_TYPE, REQUEST_PAUSE)
            return intent
        }
    }

    override fun onReceive(context: Context, intent: Intent) {
        when (intent.getIntExtra(EXTRA_CONTROL_TYPE, 0)) {
            REQUEST_PAUSE -> pipModeActionsListener.onPauseClick()
            REQUEST_PLAY -> pipModeActionsListener.onPlayClick()
        }
    }
}
interface PiPModeActionsListener {
    fun onPlayClick()
    fun onPauseClick()
}
class MainActivity : AppCompatActivity(), PiPModeActionsListener {

  ...

  override fun onPauseClick() {
      playerControlView.player?.pause()
      val params = paramsBuilder
          .setActions(getPlayAction())
          .build()
      setPictureInPictureParams(params)
  }
  override fun onPlayClick() {
      playerControlView.player?.play()
      val params = paramsBuilder
          .setActions(getPauseAction())
          .build()
      setPictureInPictureParams(params)
  }
}

Activity lifecycle in PiP-mode

BackStack activity in PiP-mode

As you have already noticed, enterPictureInPictureMode() switches to PiP-mode only the current one activity. If your application is built on the multi-activity approach, then various bugs may occur c backstack activity.
This is due to the fact that after exiting PiP-mode activity will appear in the new Task(more about activities task). Video example:

To fix this problem, add launchMode to our activity V AndroidManifest.xml.

<activity
    ...
    android:launchMode="singleTask">

In short, while launchMode we have in task There can be only one instance of this activity(more about launchMode)

Exit PiP-mode

It may not be obvious that the activity in PiP-mode is not destroyed after closing, but goes into a minimized state (onPause).
This can be fixed, for example, like this:

class MainActivity : AppCompatActivity() {
    
    ...

    override fun onStop() {
        super.onStop()
        if (isInPictureInPictureMode) {
            finish()
        }
    }
}
//AndroidManifest.xml
<activity
    ...
    android:autoRemoveFromRecents="true">
class MainActivity : AppCompatActivity() {
    
    ...

    override fun onStop() {
        super.onStop()
        if (isInPictureInPictureMode) {
            finishAndRemoveTask()
        }
    }
}

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *