Реализация 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 дней. Стоимость рассчитывается индивидуально.







