Fixing React Native WebRTC Video Resizing to Fullscreen in Android 14 PiP
Understanding the Android 14 WebRTC Picture-in-Picture (PiP) Bug
Developers implementing video calls in React Native often encounter subtle, platform-specific bugs during operating system upgrades. A particularly frustrating issue occurs on Android 14 (API level 34): when entering Picture-in-Picture (PiP) mode during a WebRTC call, tapping the PiP window causes the local (or remote) RTCVideoView to abruptly resize or stretch to full-screen dimensions inside the miniature PiP window.
This behavior works flawlessly on Android 12 and 13, but breaks on Android 14. Here is an in-depth breakdown of why this happens and how to resolve it permanently.
Why Android 14 Triggers the Fullscreen Layout Glitch
The root cause lies in a combination of Android 14 layout lifecycle updates, React Native thread scheduling in PiP, and WebRTC's SurfaceViewRenderer calculation:
- Android 14 Overlay & Focus Changes: In Android 14, tapping a PiP window brings up system PiP action controls and dispatches window focus changes differently. This triggers a layout invalidation pass across the view hierarchy.
- Window Metric vs. DecorView Bounds: When the PiP window is tapped, the native Android view hierarchy triggers a layout pass. If React Native or the native WebRTC container queries
WindowManageror rootDecorViewmetrics instead of the PiP viewport bounds, the nativeSurfaceViewRendererassumes standard activity dimensions. - JavaScript Bridge Throttling: When an app is in PiP, Android deprioritizes background threads, including the React Native JavaScript bridge. If your styling relies on JS-driven state updates (such as dimensions from
Dimensions.get('window')), these events are delayed until the user taps the window, causing an out-of-sync layout recalculation. requestLayout()Side Effects: Callingwindow.decorView.requestLayout()while in PiP forces the root decor view to remeasure its children, often using stale full-screen constraints on Android 14.
How to Fix the WebRTC PiP Resize Issue
1. Ensure Proper AndroidManifest.xml Configuration
Ensure your MainActivity has all required PiP configuration flags so that Android does not recreate the activity or dispatch erratic layout passes when transitioning into PiP or receiving user interaction:
<activity
android:name=".MainActivity"
android:exported="true"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|uiMode"
android:launchMode="singleTask">
</activity>2. Avoid JS-Driven Dimensions inside the PiP Container
Never rely on Dimensions.get('window') or useWindowDimensions() inside views meant to be visible during PiP. On Android 14, these values may reflect the pre-PiP device screen width/height rather than the PiP window.
Use pure Flexbox constraints (flex: 1) with absolute fill rules:
import React from 'react';
import { StyleSheet, View } from 'react-native';
import { RTCView } from 'react-native-webrtc';
interface VideoCallProps {
localStreamUrl?: string;
remoteStreamUrl?: string;
isPipMode: boolean;
}
export const VideoCallScreen: React.FC<VideoCallProps> = ({
localStreamUrl,
remoteStreamUrl,
isPipMode,
}) => {
return (
<View style={styles.container}>
{/* Remote Full View */}
{remoteStreamUrl && (
<RTCView
streamURL={remoteStreamUrl}
style={StyleSheet.absoluteFillObject}
objectFit="cover"
zOrder={0}
/>
)}
{/* Local Video Thumbnail / Inset View */}
{localStreamUrl && (
<View
style={[
styles.localVideoContainer,
isPipMode && styles.localVideoPipHidden,
]}>
<RTCView
streamURL={localStreamUrl}
style={styles.localVideo}
objectFit="cover"
zOrder={1}
/>
</View>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
localVideoContainer: {
position: 'absolute',
bottom: 24,
right: 24,
width: 100,
height: 150,
borderRadius: 8,
overflow: 'hidden',
elevation: 4,
},
localVideoPipHidden: {
// If you prefer to hide local view inside small PiP window
display: 'none',
},
localVideo: {
width: '100%',
height: '100%',
},
});3. Handle PiP and Avoid Manual requestLayout() in Kotlin
Remove calls to window.decorView.requestLayout() inside onPictureInPictureModeChanged. Instead, let Android's native windowing system manage the surface aspect ratio via PictureInPictureParams.
package com.yourapp
import android.app.PictureInPictureParams
import android.content.res.Configuration
import android.os.Build
import android.util.Rational
import com.facebook.react.ReactActivity
class MainActivity : ReactActivity() {
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
// Notify React Native bridge
PipAndroidModule.pipModeChanged(isInPictureInPictureMode)
}
fun updatePipParams(aspectRatio: Rational = Rational(9, 16)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val builder = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setAutoEnterEnabled(true)
builder.setSeamlessResizeEnabled(true)
}
setPictureInPictureParams(builder.build())
}
}
}4. Switch from SurfaceView to TextureView if Distortion Persists
WebRTC renders via SurfaceViewRenderer by default. In complex multi-view or PiP scenarios, SurfaceView uses a separate dedicated drawing surface behind or in front of the window, which can detach from parent React Native layout bounds during Android 14 tap-to-focus interactions.
If you encounter persistent aspect ratio stretching:
- Pass
mirror={false}and ensureobjectFit="cover"is set explicitly on theRTCViewcomponent. - If using a custom native WebRTC module, consider wrapping the video inside a
TextureViewinstead of aSurfaceView, which participates directly in the normal Android View hierarchy layout passes.
Summary
The Android 14 PiP resizing bug in React Native WebRTC stems from deferred layout passes and decor view measurements triggered when the PiP window is focused. By keeping layouts bound to strict Flexbox constraints (avoiding JS-based pixel calculations), preventing manual decor view layout passes, and configuring seamless PiP parameters natively, your WebRTC video streams will render seamlessly across all Android versions.