Picture-in-Picture Mode
Picture-in-Picture (PiP) allows users to watch videos in a small floating window while using other apps. This guide shows you how to implement PiP in React Native Video.
- iOS: Supported on iOS 14+ and iPadOS
- Android: Supported on Android 8.0 (API 26) and above
- visionOS: Supported
Basic Setup
Enable PiP in VideoView
import { VideoView, useVideoPlayer } from 'react-native-video';
import { View } from 'react-native';
export function PiPPlayer() {
const player = useVideoPlayer('https://example.com/video.mp4');
return (
<View style={{ flex: 1 }}>
<VideoView
player={player}
pictureInPicture={true}
controls={true}
style={{ width: '100%', height: 300 }}
/>
</View>
);
}
When pictureInPicture={true}, the native controls will show a PiP button (platform support permitting).
Programmatic Control
Control PiP mode programmatically using a ref:
import { VideoView, useVideoPlayer, VideoViewRef } from 'react-native-video';
import { useRef } from 'react';
import { View, Button } from 'react-native';
export function PiPWithControls() {
const player = useVideoPlayer('https://example.com/video.mp4');
const videoRef = useRef<VideoViewRef>(null);
const enterPiP = () => {
videoRef.current?.enterPictureInPicture();
};
const exitPiP = () => {
videoRef.current?.exitPictureInPicture();
};
const checkPiPSupport = () => {
const canEnter = videoRef.current?.canEnterPictureInPicture();
console.log('PiP supported:', canEnter);
};
return (
<View style={{ flex: 1 }}>
<VideoView
ref={videoRef}
player={player}
pictureInPicture={true}
style={{ width: '100%', height: 300 }}
/>
<View style={{ padding: 20, gap: 10 }}>
<Button title="Enter PiP" onPress={enterPiP} />
<Button title="Exit PiP" onPress={exitPiP} />
<Button title="Check PiP Support" onPress={checkPiPSupport} />
</View>
</View>
);
}
VideoView PiP Methods
| Method | Description |
|---|
enterPictureInPicture() | Enter picture-in-picture mode |
exitPictureInPicture() | Exit picture-in-picture mode |
canEnterPictureInPicture() | Check if PiP is supported and available |
Auto-Enter PiP
Automatically enter PiP when the app goes to background:
<VideoView
player={player}
pictureInPicture={true}
autoEnterPictureInPicture={true}
style={{ width: '100%', height: 300 }}
/>
autoEnterPictureInPicture will attempt to enter PiP mode when the video is playing and the app is backgrounded. Behavior may vary by platform.
PiP Events
Listen to PiP state changes using event callbacks:
import { VideoView, useVideoPlayer } from 'react-native-video';
import { useState } from 'react';
import { View, Text } from 'react-native';
export function PiPWithEvents() {
const player = useVideoPlayer('https://example.com/video.mp4');
const [isPiPActive, setIsPiPActive] = useState(false);
return (
<View style={{ flex: 1 }}>
<VideoView
player={player}
pictureInPicture={true}
controls={true}
onPictureInPictureChange={(isActive) => {
setIsPiPActive(isActive);
console.log('PiP mode:', isActive ? 'Active' : 'Inactive');
}}
willEnterPictureInPicture={() => {
console.log('About to enter PiP');
}}
willExitPictureInPicture={() => {
console.log('About to exit PiP');
}}
style={{ width: '100%', height: 300 }}
/>
<Text style={{ padding: 20 }}>
PiP Status: {isPiPActive ? '🎬 Active' : '❌ Inactive'}
</Text>
</View>
);
}
PiP Event Callbacks
| Event | Type | Description |
|---|
onPictureInPictureChange | (isActive: boolean) => void | Fired when PiP mode starts or stops |
willEnterPictureInPicture | () => void | Fired just before entering PiP mode |
willExitPictureInPicture | () => void | Fired just before exiting PiP mode |
Using addEventListener
You can also add PiP event listeners directly via the ref:
import { VideoView, useVideoPlayer, VideoViewRef } from 'react-native-video';
import { useRef, useEffect } from 'react';
export function PiPWithListeners() {
const player = useVideoPlayer('https://example.com/video.mp4');
const videoRef = useRef<VideoViewRef>(null);
useEffect(() => {
const subscription = videoRef.current?.addEventListener(
'onPictureInPictureChange',
(isActive) => {
console.log('PiP state changed:', isActive);
}
);
return () => {
subscription?.remove();
};
}, []);
return (
<VideoView
ref={videoRef}
player={player}
pictureInPicture={true}
style={{ width: '100%', height: 300 }}
/>
);
}
iOS Configuration
Add the Audio background mode to your Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
In Xcode, go to your target’s “Signing & Capabilities” and add the “Background Modes” capability with “Audio, AirPlay, and Picture in Picture” enabled.
Android Configuration
Update AndroidManifest.xml
Add PiP support to your activity:
<activity
android:name=".MainActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
Handle PiP in Activity (Optional)
For custom PiP behavior, you can override in MainActivity.java:
import android.app.PictureInPictureParams;
import android.util.Rational;
import android.os.Build;
@Override
public void onUserLeaveHint() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(16, 9))
.build();
enterPictureInPictureMode(params);
}
}
Complete Example with State Management
import { VideoView, useVideoPlayer, VideoViewRef } from 'react-native-video';
import { useRef, useState, useEffect } from 'react';
import { View, Button, Text, StyleSheet, AppState } from 'react-native';
export function AdvancedPiPPlayer() {
const player = useVideoPlayer('https://www.w3schools.com/html/mov_bbb.mp4');
const videoRef = useRef<VideoViewRef>(null);
const [isPiPActive, setIsPiPActive] = useState(false);
const [isPiPSupported, setIsPiPSupported] = useState(false);
useEffect(() => {
// Check PiP support on mount
const canEnter = videoRef.current?.canEnterPictureInPicture();
setIsPiPSupported(canEnter ?? false);
}, []);
useEffect(() => {
// Handle app state changes
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'background' && player.isPlaying && isPiPSupported) {
// Automatically enter PiP when app goes to background
videoRef.current?.enterPictureInPicture();
}
});
return () => {
subscription.remove();
};
}, [player.isPlaying, isPiPSupported]);
const togglePiP = () => {
if (isPiPActive) {
videoRef.current?.exitPictureInPicture();
} else {
videoRef.current?.enterPictureInPicture();
}
};
return (
<View style={styles.container}>
<VideoView
ref={videoRef}
player={player}
pictureInPicture={true}
controls={true}
onPictureInPictureChange={(isActive) => {
setIsPiPActive(isActive);
}}
willEnterPictureInPicture={() => {
console.log('Entering PiP mode');
}}
willExitPictureInPicture={() => {
console.log('Exiting PiP mode');
}}
style={styles.video}
/>
<View style={styles.info}>
<Text style={styles.status}>
PiP Support: {isPiPSupported ? '✅ Available' : '❌ Not Available'}
</Text>
<Text style={styles.status}>
PiP Status: {isPiPActive ? '🎬 Active' : '⏸️ Inactive'}
</Text>
</View>
<View style={styles.controls}>
<Button
title={isPiPActive ? 'Exit PiP' : 'Enter PiP'}
onPress={togglePiP}
disabled={!isPiPSupported}
/>
<Button
title={player.isPlaying ? 'Pause' : 'Play'}
onPress={() => (player.isPlaying ? player.pause() : player.play())}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
video: {
width: '100%',
height: 300,
},
info: {
padding: 20,
backgroundColor: '#fff',
},
status: {
fontSize: 16,
marginBottom: 8,
},
controls: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 20,
backgroundColor: '#fff',
},
});
Best Practices
1. Check PiP Support
Always verify PiP is available before attempting to use it:
const isPiPAvailable = videoRef.current?.canEnterPictureInPicture();
if (isPiPAvailable) {
videoRef.current?.enterPictureInPicture();
}
2. Handle State Changes
Respond to PiP events to update your UI:
<VideoView
onPictureInPictureChange={(isActive) => {
if (isActive) {
// Update UI for PiP mode (hide controls, etc.)
} else {
// Restore normal UI
}
}}
/>
3. Enable Background Playback
For smooth PiP experience, enable background playback:
const player = useVideoPlayer('https://example.com/video.mp4', (player) => {
player.playInBackground = true;
});
4. Maintain Playback State
Ensure video continues playing in PiP:
useEffect(() => {
const subscription = videoRef.current?.addEventListener(
'onPictureInPictureChange',
(isActive) => {
if (isActive && !player.isPlaying) {
player.play();
}
}
);
return () => subscription?.remove();
}, []);
iOS
- PiP requires iOS 14+ or iPadOS
- Background audio mode must be enabled in capabilities
- The video must have audio (or be playing) to enter PiP
- System controls in PiP window are provided automatically
Android
- PiP requires Android 8.0 (API 26)+
- The activity must declare
android:supportsPictureInPicture="true"
- PiP window aspect ratio can be customized (typically 16:9 for video)
- Some devices may have PiP disabled by the manufacturer
Troubleshooting
- Ensure
pictureInPicture={true} is set
- Verify
controls={true} is enabled
- Check platform support (iOS 14+, Android 8+)
- Confirm background modes are enabled (iOS)
PiP Not Working on Android
- Check
AndroidManifest.xml has android:supportsPictureInPicture="true"
- Verify Android version is 8.0 or higher
- Some Android devices disable PiP in settings - check device PiP settings
Video Stops When Entering PiP
- Enable background playback:
player.playInBackground = true
- On iOS, ensure audio background mode is configured
Can’t Enter PiP Programmatically
- Call
canEnterPictureInPicture() first to verify support
- Ensure video is loaded and playing
- Check that the ref is properly attached to VideoView
Next Steps