Implementing Picture-in-Picture Video Player in Mobile Applications
PiP — floating video window that stays on top of other apps when user leaves player screen. Native support exists on iOS 14+ and Android 8+, but connecting it correctly is more than five lines of code.
iOS: AVPictureInPictureController
Requirements: AVPlayerLayer or AVPlayerViewController, background mode audio-video in Info.plist, com.apple.security.application-groups permission if needed.
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
// Check support before showing button
if AVPictureInPictureController.isPictureInPictureSupported() {
pipButton.isHidden = false
}
// Start PiP
pipController.startPictureInPicture()
Delegate AVPictureInPictureControllerDelegate notifies on start, stop and errors. pictureInPictureControllerWillStartPictureInPicture — good place to hide custom controls in main view.
For SwiftUI: VideoPlayer from AVKit + modifier .onAppear with PiP setup via AVPlayerViewController.allowsPictureInPicturePlayback = true.
Custom content (not AVPlayer). iOS 15+ supports AVPictureInPictureController with contentSource: AVPictureInPictureControllerContentSource(sampleBufferDisplayLayer:) — can show any CMSampleBuffer, including WebRTC stream or AR scene data.
Android: PictureInPictureParams
// In AndroidManifest.xml
// android:supportsPictureInPicture="true"
// android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.setActions(buildPipActions()) // play/pause/close buttons
.build()
enterPictureInPictureMode(params)
setActions takes list of RemoteAction — PendingIntent buttons in PiP window. Use BroadcastReceiver for handling: pause click in PiP should stop player and update button icon via updated PictureInPictureParams.
Track PiP transition via onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) — hide UI elements, keep only PlayerView.
Auto PiP on Background
Android 12+: setAutoEnterEnabled(true) in PictureInPictureParams — app auto-enters PiP when Home pressed without code call. On Android 8–11, call enterPictureInPictureMode() in onUserLeaveHint().
iOS: AVPictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true (iOS 14.2+) — auto-start when leaving app.
Flutter: platform channels
No native Flutter API for PiP. Implement via MethodChannel: on button click in Flutter, call native method that starts PiP. Pass state (entered/exited PiP) back via EventChannel. For video_player (pub.dev), forks with PiP support exist, but stability depends on plugin version.
PiP Window Size and Position
iOS doesn't allow programmatic PiP position — user drags it. Size determined by system based on preferredContentSize and video aspect ratio. Can't force PiP window "large".
Android: PiP window size determined by setAspectRatio(Rational) — set aspect ratio, system computes size. On most Android devices, PiP occupies 30–40% of screen width.
What to Test
PiP behavior heavily depends on OS version and device manufacturer. On Android: Samsung One UI 5 has its limitations on setAspectRatio — square ratios (1:1) may display incorrectly. MIUI (Xiaomi) blocks PiP by default for third-party apps, requires explicit user permission in settings.
On iOS: PiP unavailable on iPhone with iOS 13 and below, also iPad with iOS 13 using AVPlayerViewController without additional setup. Test on minimum supported app version.
Timeline
PiP on one platform (iOS or Android) — 2 days including testing on multiple OS versions. Both platforms + Flutter — 3–4 days.







