Picture-in-Picture (PiP) mode is one of the most useful features for media-based mobile applications. It allows users to continue watching video content while navigating outside the app.
In this article, we will build a modern React Native PiP Mode Proof of Concept using:
- React Native
- Expo
- TypeScript
- react-native-video
- Native Android PiP APIs
What is Picture-in-Picture (PiP)?
Picture-in-Picture mode allows a video player to continue playing inside a small floating window while users interact with other applications.
Popular apps using PiP:
- YouTube
- Google Meet
- Netflix
- WhatsApp Video Calls
Final Result
Features implemented:
- Floating mini-player
- Background playback
- Auto-enter PiP mode
- Manual PiP trigger
- Modern UI
- Reusable custom hook
- Native Android integration
Source Code
The complete source code for this project is available on GitHub:
๐ GitHub Repository: https://github.com/dainyjose/pip-video-player-mobile
Create Project
Expo Project Setup
npx create-expo-app react-native-pip-poc
Move into the project:
cd react-native-pip-poc
Install Dependencies
Install video package:
npm install react-native-video
Project Structure
Instead of putting everything inside App.tsx, use a scalable architecture.
src/
โโโ components/
โ โโโ MiniPlayer.tsx
โ โโโ PipButton.tsx
โ โโโ PipStatus.tsx
โ โโโ VideoPlayer.tsx
โ
โโโ hooks/
โ โโโ usePictureInPicture.ts
โ
โโโ constants/
โ โโโ video.ts
โ
โโโ screens/
โ โโโ PlayerScreen.tsx
โ
โโโ types/
โโโ video.ts
This keeps the code reusable and production-ready.
Android PiP Configuration
AndroidManifest.xml
Open:
android/app/src/main/AndroidManifest.xml
Add:
<activity
android:name=".MainActivity"
android:supportsPictureInPicture="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
This enables native Android PiP support.
Configure MainActivity.kt
Open:
android/app/src/main/java/.../MainActivity.kt
Add:
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val params =
PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.build()
enterPictureInPictureMode(params)
}
}
This automatically enters PiP mode when the app moves to the background.
Create Reusable PiP Hook
usePictureInPicture.ts
Instead of writing all logic directly inside the screen, create a reusable hook.
import { useCallback, useRef, useState } from "react";
import { Platform } from "react-native";
import { VideoRef } from "react-native-video";
export const usePictureInPicture = (
videoRef: React.RefObject<VideoRef>,
) => {
const loadedRef = useRef(false);
const pendingPipRef = useRef(false);
const [pipActive, setPipActive] = useState(false);
const [paused, setPaused] = useState(true);
const enterPip = useCallback(() => {
const delays =
Platform.OS === "ios"
? [250, 700, 1200]
: [300];
delays.forEach((delay) => {
setTimeout(() => {
videoRef.current?.enterPictureInPicture();
}, delay);
});
}, []);
const activateFloatingPlayer = () => {
pendingPipRef.current = true;
setPaused(false);
if (loadedRef.current) {
enterPip();
}
};
return {
paused,
setPaused,
pipActive,
setPipActive,
loadedRef,
activateFloatingPlayer,
};
};
Benefits:
- reusable logic
- cleaner screens
- easier maintenance
- scalable architecture
Build Video Player Component
VideoPlayer.tsx
import Video from "react-native-video";
<Video
ref={videoRef}
source={VIDEO_SOURCE}
style={styles.video}
resizeMode="contain"
paused={paused}
controls
playInBackground
playWhenInactive
enterPictureInPictureOnLeave
ignoreSilentSwitch="ignore"
/>
Important PiP props:
| Prop | Purpose |
|---|---|
| playInBackground | Continue playback in background |
| playWhenInactive | Allow inactive playback |
| enterPictureInPictureOnLeave | Auto-enter PiP |
Auto PiP on Background
Inside the screen:
useEffect(() => {
const sub = AppState.addEventListener(
"change",
(state) => {
if (state === "background") {
activateFloatingPlayer();
}
},
);
return () => sub.remove();
}, []);
This creates a YouTube-like experience.
Modern UI Layout
Instead of basic buttons, create:
- Player card
- Floating mini-player
- Status cards
- Dark streaming UI
Example:
<View style={styles.playerCard}>
<VideoPlayer />
<PipButton />
<PipStatus />
</View>
Running the Project
Android
npx expo run:android
Important Notes
Expo Go Limitation
Expo Go may not fully support native PiP functionality.
Use native builds instead.
Common Issues
PiP Not Starting
Check:
- Android version >= 8
- Manifest configuration
- Native build usage
- Video loaded state
App Crashes
Verify:
- correct MainActivity.kt imports
- react-native-video installation
- Android SDK compatibility
Future Improvements
This PoC can be extended with:
- Custom PiP controls
- Live streaming
- Video call PiP
- Draggable mini-player
- Media session controls
- Netflix-style player UI
Conclusion
Picture-in-Picture mode significantly improves user experience for media applications.
With React Native, Expo, and native Android APIs, we can build scalable and modern PiP experiences similar to YouTube and Netflix.
This project demonstrates:
- native Android PiP integration
- modular React Native architecture
- reusable hooks
- background playback handling
- scalable UI structure
You can now extend this implementation into:
- OTT applications
- Video conferencing apps
- Live class platforms
- Social media video apps
- Streaming platforms
โ๏ธ Written by Dainy Jose โ React Native Mobile Application Developer with 3+ years of experience building cross-platform mobile apps using React Native (Expo, TypeScript, Redux).
Currently expanding backend knowledge through the MERN Stack (MongoDB, Express.js, React.js, Node.js) to create more efficient, full-stack mobile experiences.
๐ผ Tech Stack: React Native ยท TypeScript ยท Redux ยท Expo ยท Firebase ยท Node.js ยท Express.js ยท MongoDB ยท REST API ยท JWT ยท Jest ยท Google Maps ยท Razorpay ยท PayU ยท Agile ยท SDLC ยท Git ยท Bitbucket ยท Jira
๐ฌ Connect with me:
๐ Portfolio
๐ LinkedIn
๐ป GitHub
Top comments (0)