Ever wanted to watch videos on your Android phone while you browse the web? Everyone always desires this. Thanks to Android Oreo, we now have Picture In Picture feature to develop awesome Android Applications.

In this tutorial, we’ll be developing an application where we can view a Video in a preview while we browse through the list of our videos or other applications on our smartphone. You would see this behavior in YouTube or FaceBook app while watching videos and doing other things at the same time in the app.

Android Picture In Picture

Picture In Picture support came up for Android TV with the Android Nougat update. So it was expected to be there for phones in Oreo. By default, Picture In Picture (PiP) feature isn’t enabled for activities.

So you need to set the following attributes in the AndroidManifest.xml file for the activity which requires PiP.

<activity android:name=".PiPActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:label="@string/title_activity_pi_p" android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true" android:theme="@style/AppTheme.NoActionBar" />

android:configChanges must be set to make sure that the Activity does not lose any data while changing the screen size particularly.

android:resizeableActivity makes sure that the activity can be resized when the PiP is triggered.

In our Activity there are three major methods for PiP support:

enterPictureInPictureMode() : We pass an instance of the PiP builder in here. This method is what starts the PiP.

: We pass an instance of the PiP builder in here. This method is what starts the PiP. onPictureInPictureModeChanged() : gets triggered when the PiP has occurred. Here we can hide the UI elements which we don’t what in the PiP enabled mode.

: gets triggered when the PiP has occurred. Here we can hide the UI elements which we don’t what in the PiP enabled mode. onUserLeaveHint() : This gets triggered when the user presses home/recents options to come outside the application. We can set the PiP inside it. We need to make sure that we don’t pause/stop the video in the onPause()/onStop() methods.

Now let the code do the rest of the talking. In the following section, we’ll create a RecyclerView which contains a list of Videos. The Videos would be played in the VideoView.

We’ve used a public set of video urls available here

Android Picture in Picture Project Structure

Android Picture in Picture Code

The code for the activity_main.xml layout is given below:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

Each row of the RecyclerView is populated from cardview_item_row.xml file:

<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:id="@+id/cardView" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <TextView android:id="@+id/txtName" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </android.support.v7.widget.CardView>

The code for the MainActivity.java class is given below:

package com.journaldev.androidpictureinpicture; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import java.util.ArrayList; public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.ItemListener { private ArrayList<Model> videoList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setItemAnimator(new DefaultItemAnimator()); recyclerView.setLayoutManager(new LinearLayoutManager(this)); populateArrayList(); RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(videoList, this); recyclerView.setAdapter(recyclerViewAdapter); } private void populateArrayList() { videoList.add(new Model("Big Buck Bunny", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")); videoList.add(new Model("We are going on bull run", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4")); videoList.add(new Model("Volkswagen GTI Review", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/VolkswagenGTIReview.mp4")); videoList.add(new Model("For Bigger Blazes", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4")); videoList.add(new Model("Subaru Outback On Street And Dirt", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4")); videoList.add(new Model("What care can you get for ten grand?", "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4")); } @Override public void onItemClick(Model model) { startActivity(new Intent(this, PiPActivity.class).putExtra("videoUrl", model.videoUrl)); } }

We’ve created a Model.java class which holds the data for the RecyclerViewAdapter class.

package com.journaldev.androidpictureinpicture; public class Model { public String name, videoUrl; public Model(String name, String videoUrl) { this.name = name; this.videoUrl = videoUrl; } }

The code for the RecyclerViewAdapter.java class is given below:

package com.journaldev.androidpictureinpicture; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.CryptoViewHolder> { private ArrayList<Model> videoList; private ItemListener mItemListener; public class CryptoViewHolder extends RecyclerView.ViewHolder { private TextView mName; private CardView cardView; public CryptoViewHolder(View itemView) { super(itemView); mName = itemView.findViewById(R.id.txtName); cardView = itemView.findViewById(R.id.cardView); } } public RecyclerViewAdapter(ArrayList<Model> videoList, ItemListener itemListener) { this.videoList = videoList; this.mItemListener = itemListener; } @Override public CryptoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_item_row, parent, false); return new CryptoViewHolder(itemView); } @Override public void onBindViewHolder(CryptoViewHolder holder, final int position) { holder.mName.setText(videoList.get(position).name); holder.cardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mItemListener.onItemClick(videoList.get(position)); } }); } @Override public int getItemCount() { return videoList.size(); } public interface ItemListener { void onItemClick(Model model); } }

onItemClick triggers a call to the PiPActivity with the videoUrl being passed.

The code for the activity_pip.xml class is given below:

<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:id="@+id/coordinatorLayout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".PiPActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_pi_p" /> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" app:srcCompat="@android:drawable/ic_menu_set_as" /> </android.support.design.widget.CoordinatorLayout>

The code for the content_pi_p.xml is given below:

<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".PiPActivity" tools:showIn="@layout/activity_pip"> <VideoView android:id="@+id/videoView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout>

The code for the PiPActivity.java class is given below:

package com.journaldev.androidpictureinpicture; import android.app.PictureInPictureParams; import android.content.Intent; import android.content.res.Configuration; import android.media.MediaPlayer; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.RequiresApi; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.Rational; import android.view.View; import android.widget.MediaController; import android.widget.VideoView; @RequiresApi(api = Build.VERSION_CODES.O) public class PiPActivity extends AppCompatActivity { VideoView videoView; FloatingActionButton fab; PictureInPictureParams.Builder pictureInPictureParamsBuilder = new PictureInPictureParams.Builder(); Toolbar toolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pip); toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); videoView = findViewById(R.id.videoView); fab = findViewById(R.id.fab); final MediaController mediacontroller = new MediaController(this); mediacontroller.setAnchorView(videoView); String videoUrl = getIntent().getStringExtra("videoUrl"); videoView.setMediaController(mediacontroller); videoView.setVideoURI(Uri.parse(videoUrl)); videoView.requestFocus(); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startPictureInPictureFeature(); } }); videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mp.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { videoView.setMediaController(mediacontroller); mediacontroller.setAnchorView(videoView); } }); } }); } private void startPictureInPictureFeature() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Rational aspectRatio = new Rational(videoView.getWidth(), videoView.getHeight()); pictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build(); enterPictureInPictureMode(pictureInPictureParamsBuilder.build()); } } @Override public void onUserLeaveHint() { if (!isInPictureInPictureMode()) { Rational aspectRatio = new Rational(videoView.getWidth(), videoView.getHeight()); pictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build(); enterPictureInPictureMode(pictureInPictureParamsBuilder.build()); } } @Override public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { if (isInPictureInPictureMode) { fab.setVisibility(View.GONE); toolbar.setVisibility(View.GONE); } else { fab.setVisibility(View.VISIBLE); toolbar.setVisibility(View.VISIBLE); } } @Override public void onNewIntent(Intent i) { updateVideoView(i); } private void updateVideoView(Intent i) { String videoUrl = i.getStringExtra("videoUrl"); videoView.setVideoURI(Uri.parse(videoUrl)); videoView.requestFocus(); } }

In the startPictureInPictureFeature() we change the width and height of the VideoView to a preview size before starting the picture in picture mode.

Inside the onNewIntent() method, we update the VideoView.

Inside the onPictureInPictureModeChanged we hide the toolbar as well as the FloatingActionButton.

onNewIntent gets triggered when an intent to an existing activity is done. In this example, it makes sense to keep a single instance of the PiP Activity.

Our AndroidManifest.xml file is defined below:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="https://schemas.android.com/apk/res/android" package="com.journaldev.androidpictureinpicture"> <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".PiPActivity" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:label="@string/title_activity_pi_p" android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true" android:theme="@style/AppTheme.NoActionBar" /> </application> </manifest>

PictureInPicture feature provides an inbuilt icon to resize the preview and as well as drag it on the screen anywhere.

The output of the above application in action is given below:



Just double tap the VideoView to enable MediaControls.

Look how easy it is to implement PictureInPicture in your Android Application!

This brings an end to this tutorial. You can download the project from the link below: