Android Picture-in-Picture feature helper library
This repository holds an Android Library that helps with the use of Picture-in-Picture mode. It also includes a sample app that shows how to use the library.
More to learn about Android PiP API: https://youtu.be/bvCKd_XctNg
Add the dependencies to your project:
// the core library
implementation("com.mohsenoid.pip:pip-core:1.0.0")
// the UI library including ViewGroups which are PiP aware
implementation("com.mohsenoid.pip:pip-ui:1.0.0")
First you need to initialize the library inside you application class:
class App : Application() {
override fun onCreate() {
super.onCreate()
Pip.init(this)
}
}
By extending the PipSupportAppCompatActivity
you can easily use the Picture-in-Picture mode.
class PlayerActivity : PipSupportAppCompatActivity() {
// ...
}
Make sure that your PlayerActivity is setup correctly for PiP mode:
<activity android:name=".PlayerActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"
android:supportsPictureInPicture="true" />
You can inform the PiP library about your player status changes so that it can update the PiP auto enter accordingly:
private fun setupPlayer() {
// ...
val exoPlayer = ExoPlayer.Builder(this).build().apply {
// ...
addListener(
object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
updatePlayerState(isPlaying)
}
},
)
}
// ...
}
You can set the Player rectangle so that the PiP library can use it for a smooth animation when entering/exiting PiP mode:
val rect = Rect()
binding.player.getGlobalVisibleRect(rect)
updatePipRect(rect)
You can pass actions to the PiP library so that it can show them in the PiP window:
updatePipActions(listOfNotNull(getPipAction()))
private fun getPipAction(): RemoteAction? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val actionUri = Uri.parse("https://youtube.com/AndroidDeveloperTips")
val actionIntent = Intent(Intent.ACTION_VIEW, actionUri)
val actionPendingIntent =
PendingIntent.getActivity(this, 0, actionIntent, PendingIntent.FLAG_IMMUTABLE)
val remoteAction = RemoteAction(
Icon.createWithResource(this, R.drawable.ic_picture_in_picture_action),
"More info",
"More info action",
actionPendingIntent,
)
remoteAction
} else {
null
}
}
By getting this controller interface you can control the PiP mode:
private val pipController = Pip.mgetPipController()
Use it to enter PiP mode if allowed:
pipCommander.enterPip(
{ alreadyInPipMode ->
// switching to PiP was successful
},
{ pipEnterError ->
// switching to PiP was not successful
},
)
Or to exit PiP mode:
pipCommander.closePip()
You may also pass a content ID to the updatePlayerState
method and use that to close the correct
PiP window:
updatePlayerState(isPlaying, contentId)
// ...
pipCommander.closePip(contentId)
By getting this observable interface you can observe the PiP mode change:
private val pipObservable = Pip.getPipObservable()
You can check if the PiP mode is allowed based on player state and device for instance to enabling/disabling PiP mode button:
lifecycleScope.launch {
pipObservable.isPipAllowedStateFlow.collectLatest {
repeatOnLifecycle(Lifecycle.State.STARTED) {
binding.button.isVisible = it
}
}
}
Or indicate of the player is in PiP mode or not:
lifecycleScope.launch {
pipObservable.isInPipModeStateFlow.collectLatest { isInPipMode ->
binding.player.useController = !isInPipMode
}
}
Or check if a content is being played in PiP mode:
val result = isInPipMode(contentId)
By adding the pip-ui library to your project you can use the PipAwareFrameLayout
and PipAwareConstraintLayout
in your layout files and show/hide views which should not be visible in the PiP mode:
<com.mohsenoid.pip.ui.PipAwareFrameLayout
android:id="@+id/titleContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/player">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.MaterialComponents.Headline4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="8dp"
tools:text="Video Title" />
</com.mohsenoid.pip.ui.PipAwareFrameLayout>
Just make sure to init the views once the layout is inflated:
override fun onCreate(savedInstanceState: Bundle?) {
// inflate the layout
// ...
binding.titleContainer.init()
}
Copyright 2023 Mohsen Mirhoseini
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.