Реалізація Picture-in-Picture режиму для iOS-додатку
PiP на iOS працює не автоматично: додаток має явно підтримати цей режим через AVKit API, правильно налаштувати AVAudioSession та обробити жизненний цикл PiP-вікна. Здається несложним — до тих пір, поки не починаєш розбиратися з кастомним контентом, не AVPlayer.
Базова реалізація з AVPlayerViewController
Найпростіший шлях — AVPlayerViewController. PiP з коробки: встановлюємо allowsPictureInPicturePlayback = true, налаштовуємо AVAudioSession, і кнопка PiP з'являється автоматично.
import AVKit
class VideoViewController: UIViewController {
private var player: AVPlayer!
private var playerViewController: AVPlayerViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Обов'язково: категорія playback для фонового воспроизведення
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try? AVAudioSession.sharedInstance().setActive(true)
player = AVPlayer(url: videoURL)
playerViewController = AVPlayerViewController()
playerViewController.player = player
playerViewController.allowsPictureInPicturePlayback = true
addChild(playerViewController)
view.addSubview(playerViewController.view)
playerViewController.view.frame = view.bounds
playerViewController.didMove(toParent: self)
}
}
Додати в Info.plist: UIBackgroundModes → audio. Без цього PiP продовжить показувати відео, але звук пропаде при переході в фон.
Кастомний PiP: AVPictureInPictureController
Якщо відеоплеєр кастомний (не AVPlayerViewController) — потрібен AVPictureInPictureController напрямку. Ініціалізується через AVPictureInPictureController(playerLayer:) для AVPlayerLayer чи через AVPictureInPictureControllerContentSource з AVPictureInPictureSampleBufferPlaybackDelegate для кастомного рендерингу.
class CustomVideoPlayer: UIView {
private var playerLayer: AVPlayerLayer!
private var pipController: AVPictureInPictureController?
func setupPiP() {
guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController?.delegate = self
pipController?.canStartPictureInPictureAutomaticallyFromInline = true // автостарт при уходе в фон
}
}
extension CustomVideoPlayer: AVPictureInPictureControllerDelegate {
func pictureInPictureControllerWillStartPictureInPicture(_ controller: AVPictureInPictureController) {
// сховати кастомні контролі поверх відео
}
func pictureInPicture(_ controller: AVPictureInPictureController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
// відновити UI при закритті PiP-вікна
completionHandler(true)
}
}
PiP з довільним контентом (iOS 15+)
AVPictureInPictureVideoCallViewController (iOS 15+) — для не-відеоконтенту: живе відео з камери, WebRTC, анімації. Контент рендерится у UIView, який розміщується в PiP-контейнері через AVPictureInPictureControllerContentSource(activeVideoCallSourceView:contentViewController:).
Це використовують мессенджери (FaceTime, Zoom) для відеозвинків поверх інших додатків. Ключове обмеження: контент має бути дійсно «активним вызовом» за змістом — Apple може відхилити за зловживання при ревю.
Часті проблеми
PiP не запускається при переході в фон. У 90% випадків: не встановлена canStartPictureInPictureAutomaticallyFromInline = true, чи не налаштований UIBackgroundModes: audio у Info.plist, чи AVAudioSession неактивна.
Відео зависає при повернені з PiP. У restoreUserInterfaceForPictureInPictureStopWithCompletionHandler потрібно вернути completionHandler(true) тільки після того, як UI повністю відновлен. Виклик completionHandler(true) одразу — система думає, що відновлення завершено та робить transition animation, але ваш UI ще не готовий.
Кастомні кнопки у PiP-вікні. З iOS 16 — AVPictureInPictureController.requiresLinearPlayback = false та кастомні playbackControlsIncludeTransportBar, playbackControlsIncludeInfoViews. До iOS 16 — тільки стандартні контролі.
Графіки
2–3 робочих дні для базової інтеграції PiP з AVPlayerViewController. Кастомний плеєр з AVPictureInPictureController та нестандартним контентом — до 5 днів. Вартість розраховується індивідуально.







