Implement the Picture-in-Picture (PiP) feature on Android so users can keep watching video in a floating window while using other apps.
Function introduction
Picture-in-Picture (PiP) displays a video in a small, floating window in a corner of the screen. Users can continue watching the video while using other apps, such as replying to messages or browsing the web.
Sample code
ARTC provides open-source sample code. For more information, see Implement Picture-in-Picture on Android.
Prerequisites
Before you begin, ensure the following requirement is met:
-
Your device must run Android 8.0 (API level 26) or later.
Implementation
1. Configure properties
The PiP API is scoped to an Activity. Declare the following properties for the Activity in your AndroidManifest.xml file.
-
android:supportsPictureInPicture="true"
-
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
<activity android:name=".PictureInPicture.PictureInPictureAcitivity"
android:label="@string/picture_in_picture"
android:exported="false"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:resizeableActivity="true"
android:launchMode="singleTask"
tools:targetApi="24" />
2. Enter Picture-in-Picture mode
-
Not all devices support PiP. Call the
hasSystemFeaturemethod to check for support before entering this mode. -
Call the
enterPictureInPictureModesystem API to enter PiP mode.
private void enterPIPMode(){
if(!hasJoined) {
ToastHelper.showToast(this, "Join a channel first", Toast.LENGTH_SHORT);
return;
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
Rational aspectRatio = new Rational(9, 16); // Recommended video aspect ratio
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build();
enterPictureInPictureMode(params);
} else {
ToastHelper.showToast(this, "This device does not support PiP", Toast.LENGTH_SHORT);
}
} else {
ToastHelper.showToast(this, "PiP is supported only on Android 8.0 and later", Toast.LENGTH_SHORT);
}
}
3. Respond to PiP mode changes
Override the onPictureInPictureModeChanged system callback to update the UI when the mode changes.
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
if (isInPictureInPictureMode) {
// Enter PiP mode: Hide the full-screen UI and show only the video.
findViewById(R.id.ll_channel_layout).setVisibility(View.GONE);
findViewById(R.id.scroll_remote_container).setVisibility(View.GONE);
// Optional: Hide the status bar.
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
// Optional: Create a dedicated video container for PiP.
showPipVideoContainer();
} else {
// Exit PiP mode: Restore the full-screen UI.
findViewById(R.id.ll_channel_layout).setVisibility(View.VISIBLE);
findViewById(R.id.scroll_remote_container).setVisibility(View.VISIBLE);
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
// Re-attach the local preview view.
if (mAliRtcEngine != null && mLocalVideoCanvas != null) {
mAliRtcEngine.setLocalViewConfig(mLocalVideoCanvas, AliRtcVideoTrackCamera);
}
// Remove the PiP container.
removePipVideoContainer();
}
}
/**
* Shows the main video container in PiP mode, such as the local preview.
*/
private void showPipVideoContainer() {
if (mPipVideoContainer == null) {
mPipVideoContainer = new FrameLayout(this);
mPipVideoContainer.setBackgroundColor(Color.BLACK);
addContentView(mPipVideoContainer, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// Recreate the local preview view. You cannot move a SurfaceView, so you must recreate it.
SurfaceView surfaceView = mAliRtcEngine.createRenderSurfaceView(this);
surfaceView.setZOrderOnTop(true);
surfaceView.setZOrderMediaOverlay(true);
mPipVideoContainer.addView(surfaceView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
if(mAliRtcEngine != null) {
AliRtcEngine.AliRtcVideoCanvas canvas = new AliRtcEngine.AliRtcVideoCanvas();
canvas.view = surfaceView;
mAliRtcEngine.setLocalViewConfig(canvas, AliRtcVideoTrackCamera);
}
}
}
/**
* Removes the PiP video container.
*/
private void removePipVideoContainer() {
if (mPipVideoContainer != null && mPipVideoContainer.getParent() != null) {
((ViewGroup) mPipVideoContainer.getParent()).removeView(mPipVideoContainer);
mPipVideoContainer = null;
}
}