Реалізація picture-in-picture відеоплеєра в мобільному застосунку
PiP — плаваюче відеовікно, яке залишається поверх інших застосунків коли користувач уходить з екрана плеєра. Рідна підтримка є на iOS 14+ та Android 8+, але правильно її підключити — не п'ять рядків коду.
iOS: AVPictureInPictureController
Вимоги: AVPlayerLayer або AVPlayerViewController, фоновий режим audio-video в Info.plist, дозвіл com.apple.security.application-groups при необхідності.
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
// Перевіряємо підтримку перед показом кнопки
if AVPictureInPictureController.isPictureInPictureSupported() {
pipButton.isHidden = false
}
// Запуск PiP
pipController.startPictureInPicture()
Делегат AVPictureInPictureControllerDelegate повідомляє про старт, зупинку та помилки. pictureInPictureControllerWillStartPictureInPicture — хороше місце, щоб сховати власні контролі в основному view.
Для SwiftUI: VideoPlayer з AVKit + модифікатор .onAppear з налаштуванням PiP через AVPlayerViewController.allowsPictureInPicturePlayback = true.
Користувацький контент (не AVPlayer). iOS 15+ підтримує AVPictureInPictureController з contentSource: AVPictureInPictureControllerContentSource(sampleBufferDisplayLayer:) — можна показувати будь-яку CMSampleBuffer, включаючи WebRTC-потік або дані AR-сцени.
Android: PictureInPictureParams
// У AndroidManifest.xml
// android:supportsPictureInPicture="true"
// android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.setActions(buildPipActions()) // кнопки play/pause/close
.build()
enterPictureInPictureMode(params)
setActions приймає список RemoteAction — PendingIntent-кнопки в PiP-вікні. Використовуємо BroadcastReceiver для обробки: натиск pause у PiP повинен зупинити плеєр та оновити іконку кнопки через оновлений PictureInPictureParams.
Відстежуємо перехід у PiP через onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) — ховаємо UI-елементи, залишаємо тільки PlayerView.
Автоматичний PiP при уході з екрану
Android 12+: setAutoEnterEnabled(true) у PictureInPictureParams — застосунок сам переходить у PiP при натиску Home без виклику коду. На Android 8–11 викликаємо enterPictureInPictureMode() в onUserLeaveHint().
iOS: AVPictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true (iOS 14.2+) — автозапуск при уході з застосунку.
Flutter: platform channels
Рідного Flutter API для PiP немає. Реалізуємо через MethodChannel: при натиску кнопки у Flutter викликаємо нативний метод, який запускає PiP. Стан (вошли/вишли з PiP) передаємо назад через EventChannel. Для video_player (pub.dev) є форки з PiP-підтримкою, але стабільність залежить від версії плагіна.
Розмір та позиція PiP-вікна
iOS не дозволяє програмно задати положення PiP-вікна — користувач перетягує його сам. Розмір визначається системою на основі preferredContentSize контейнера та співвідношення сторін відео. Не можна зробити PiP-вікно «великим» примусово.
Android: розмір PiP-вікна визначається setAspectRatio(Rational) — задаємо співвідношення сторін, система обчислює розмір. На більшості Android-пристроїв PiP-вікно займає 30–40% ширини екрана.
Що потрібно тестувати
PiP-поведінка сильно залежить від версії ОС та виробника пристрою. На Android: Samsung One UI 5 має свої обмеження на setAspectRatio — квадратні співвідношення (1:1) можуть відображатися некоректно. MIUI (Xiaomi) за замовчуванням блокує PiP для сторонніх застосунків і вимагає явного дозволу користувача в налаштуваннях.
На iOS: PiP недоступна на iPhone з iOS 13 та нижче, а також на iPad з iOS 13 при використанні AVPlayerViewController без додаткової налаштування. Тестуємо на мінімально підтримуваній версії застосунку.
Орієнтири за часом
PiP на одній платформі (iOS або Android) — 2 дні включаючи тестування на кількох версіях ОС. Обі платформи + Flutter — 3–4 дні.







