This guide explains what Picture-in-Picture (PiP) is, describes how to implement it and links relevant resources.
The Picture-in-Picture feature allows developers to create a floating player. This is commonly used to let the video remain playing on screen even if:
The below table makes a comparison of the available THEOplayer APIs specifically targeting in or out of app PiP use cases. This table does not represent the possibilities on the platforms with THEOplayer.
Type | Web SDK | Android SDK | Android TV SDK | iOS SDK | tvOS SDK | Roku SDK | Fire TV SDK | Chromecast SDK |
---|---|---|---|---|---|---|---|---|
in-app1 | Yes | No3 | No3 | Yes | No | No | No3 | N/A |
out-of-app2 | No | Yes | Yes | Yes | Yes | No | Yes | N/A |
1 While using in-app picture-in-picture, the visibility of the PiP window is contained to the inside of the app. In other words, it will go to background/foreground with the application.
2 While using out-of-app picture-in-picture, the visibility of the PiP windows is not contained to the inside of the app. It can remain visible while the user navigates to other views, pages or apps.
3 On Android/Fire TV SDK, there is no API to explicitly only allow in-app PiP. The existing PiP API activates both in and out of app PiP.
Picture-in-Picture is an interesting option on both desktop and mobile to keep showing the video while the user navigates to different parts of the content. PiP in THEOplayer presents several possibilities: this section shows how to enable, configure and observe the Picture-in-Picture option for all available SDKs. A distinction is made between in-app and out-of-app Picture-in-picture, where in the latter the PiP-window can also remain visible if the application is sent to background or another view is presented.
In this SDK, PiP is enabled by default if the player configuration contains any PiP configuration (see below image). For this reason, there is no need to use any specific snippet to enable it. Note that in this SDK out-of-app Picture is (currently) not supported.
The PiP configuration for this SDK includes 3 properties:
position
: (optional, possible values: "top-left", "top-right", "bottom-left", "bottom-right")visibility
: (optional, a number from 0 to 1) retainPresentationModeOnSourceChange
: (optional, possible values: true or false) var playerConfig = {
...
pip: {
visibility: 0.7,
position: "bottom-left",
retainPresentationModeOnSourceChange: true
}
};
If you want to use Picture-In-Picture in your application you can instantiate the player with a PiPConfiguration, to be passed in the THEOplayerConfiguration.
Using this PiPConfiguration you can configure whether the player should retain its presentation mode upon source-change. You can also choose whether to make use of out-of-app PiP (=nativePictureInPicture
).
Note that to make use of out-of-app PiP the minimum required iOS version is 14.0.
/* Configure whether presentation mode should be retained on source changes and whether to use native PiP */
let pipConfig = PiPConfiguration(retainPresentationModeOnSourceChange: true, nativePictureInPicture: true)
let playerConfig = THEOplayerConfiguration(... , pip: pipConfig)
var theoplayer = THEOplayer(configuration: playerConfig)
Once the Picture-In-Picture mode is enabled, the player's pip property can be accessed.
While using in-app PiP you can use the configure(movable:defaultCorner:) method of the player's pip property to configure the picture-in-picture.
You can configure:
theoplayer.pip!.configure(movable: false, defaultCorner: .bottomLeft)
Notes:
configure
is only available when using in-app PiP.If you want to use Picture-In-Picture in your application you can instantiate the player with a PiPConfiguration, to be passed in the THEOplayerConfig. When passing a PipConfiguration, the Android SDK will always make use of out-of-app PiP.
Note that to make use of out-of-app PiP the minimum required Android version is Oreo (API level 26).
val playerConfig = THEOplayerConfig.Builder()
.pipConfiguration(PipConfiguration.Builder().build())
.build()
val tpv = THEOplayerView(context, playerConfig)
Once the Picture-In-Picture mode is enabled, the player's pip property can be accessed.
To enable PiP mode with the required PiP type, you can utilize the following steps:
THEOplayerView
. This manager reference is available through the theoPlayerView
.enterPiP()
: Call the enterPiP(PiPType.ACTIVITY)
method on the PiP manager. By doing so, you initiate PiP mode using the ACTIVITY
PiP type.Here's the implementation:
theoPlayerView.piPManager?.enterPiP(PiPType.ACTIVITY)
THEOplayer supports three PiP types:
CUSTOM
empowers you as the developer to design and implement your own PiP window. You have the flexibility to tailor the PiP experience to your app's specific requirements. For details on implementing a custom PiP window, explore this Android documentation.Using the notification center you can listen to the PictureInPictureMoved notification. This notification will be pushed every time the picture-in-picture view moves to a different corner.
In the callback, you can retrieve the previous and the new corner in the userInfo dictionary respectively with the PictureInPictureOldCornerUserInfoKey and the PictureInPictureNewCornerUserInfoKey keys.
NotificationCenter.default.addObserver(self, selector: #selector(onPiPMoved), name: Notification.Name.PictureInPictureMoved, object: nil)
@objc func onPiPMoved(notif: Notification) {
let userInfo = notif.userInfo as! [String: Any]
let oldCorner : PictureInPictureCorner = userInfo[PictureInPictureOldCornerUserInfoKey]! as! PictureInPictureCorner
let newCorner : PictureInPictureCorner = userInfo[PictureInPictureNewCornerUserInfoKey]! as! PictureInPictureCorner
print("PiP has moved from \(oldCorner) to \(newCorner)")
}
By using your own implementation of the AVPictureinpictureControllerDelegate you are able to listen to a number of events. These contain, but are not limited to, when the player will enter/exit PiP, has entered/exited PiP, ... To achieve this you just have to set your implementation of the delegate as the one of the PictureInPictureController.
class CustomPiPDelegate: NSObject, AVPictureInPictureControllerDelegate {
func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
// Custom action when picture in picture started
}
// ... Others here.
}
let customDelegate = CustomPiPDelegate()
var THEOPiP = theoplayer?.pip
if #available(iOS 14.0, *) {
THEOPiP?.nativePictureInPictureDelegate = customDelegate
} else {
// Fallback on earlier versions
}
For the Android SDK it is possible to listen for a PlayerEventTypes.PRESENTATIONMODECHANGE
event or verify the presentationMode of the player in case you need to find out what the presentation mode is at a given moment or detect a change.
// Assuming you have an instance of the player called 'theoplayer'
theoplayer.addEventListener(PlayerEventTypes.PRESENTATIONMODECHANGE) { event ->
val currentMode = event.presentationMode
if (currentMode == PresentationMode.PICTURE_IN_PICTURE) {
// Currently in PiP
}
}
// The listener above will be triggered by one of:
theoPlayerView.piPManager?.enterPiP(PiPType.ACTIVITY)
theoPlayerView.piPManager?.exitPiP()
When your app uses the ACTIVITY
PiP type and the user closes the PiP window, the playback and its restoration follows these steps:
theoPlayerView.settings.setAllowBackgroundPlayback(false)
is set to false (which is the default setting).play()
method on the player instance.Here's a code snippet demonstrating how to resume playback after exiting PiP mode:
theoPlayer.addEventListener(PlayerEventTypes.PRESENTATIONMODECHANGE) {
if (it.presentationMode == PresentationMode.INLINE) {
// Playback pauses after closing PiP window
if (lifecycle.currentState == Lifecycle.State.STARTED) {
theoPlayerView.player.play()
}
}
}
For the DIALOG
PiP type, in order to correctly exit PiP window you should override onPictureInPictureModeChanged
and call piPManager.exitPiP()
.
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (!isInPictureInPictureMode) {
theoPlayerView.piPManager?.exitPiP()
}
}
This example ensures that playback pauses if an Activity that holds THEOplayer is in the background and background playback is not allowed, providing a smoother transition for users returning from PiP mode.
For the CUSTOM
PiP type, you are responsible for implementing your own PiP window and handling the necessary tasks for entering and exiting PiP mode.
Managing playback after returning from PiP mode is in your hands as a developer. You can reference the provided code snippet for the ACTIVITY
type as a starting point and adapt it to match your app's unique design and logic.
A demo of this feature can be found at
To see PiP in action, start the video and scroll down on the page.
A sample app for Picture-In-Picture could be found here:
The following remarks can help:
The following resources provide more information: