Добавить объявление

Работа с Picture-in-Picture в iOS

Picture-in-Picture - системная функция iOS, которая позволяет воспроизводить видео в отдельном плавающем окне поверх других приложений. Пользователь может свернуть приложение, продолжив смотреть контент в компактном окошке. Для разработчика видеосервиса это отдельный механизм со своей инфраструктурой, жизненным циклом и ограничениями.

Что такое PiP:


Технически PiP - это не просто маленькое видео в углу. Это сквозной системный механизм. Окно PiP не принадлежит приложению в привычном смысле - оно управляется системой и продолжает жить, даже когда пользователь перешел на рабочий стол.

Функция появилась на iPad в iOS 9, но для iPhone стала общедоступной с iOS 14. С этого момента фоновое воспроизведение видео в маленьком окошке стало стандартом для уважающих себя видеосервисов.

Подготовка и запуск:


Основной инструмент - класс AVPictureInPictureController. Создать его можно двумя способами: передать ссылку на AVPlayerLayer или использовать contentSource.

let pipController = AVPictureInPictureController(playerLayer: playerLayer)

Либо:

let contentSource = AVPictureInPictureController.ContentSource(playerLayer: playerLayer)
let pipController = AVPictureInPictureController(contentSource: contentSource)

Настройка AVAudioSession:


Чтобы система разрешила запускать видео в фоне, необходимо правильно настроить AVAudioSession. Без этого startPictureInPicture() просто не сработает.

do {
let session = AVAudioSession.sharedInstance()
try session.setCategory(.playback, mode: .moviePlayback)
try session.setActive(true)
} catch {
print("Failed to configure AVAudioSession: \(error)")
}

Почему PiP может не запуститься:


Есть две частые проблемы:

  • Контроллер не удерживается в памяти. Если создать AVPictureInPictureController локально внутри функции, ARC удалит его сразу после завершения функции. Контроллер не доживет до отрисовки окна. Нужна сильная ссылка.

  • Не настроены Background Modes. В Xcode нужно зайти в Signing & Capabilities, добавить Background Modes и поставить галочку напротив Audio, AirPlay, and Picture in Picture.

Отслеживание состояний:


Для отслеживания жизненного цикла PiP нужно реализовать протокол AVPictureInPictureControllerDelegate. Он включает методы WillStart, DidStart, FailedToStart, WillStop, Restore и DidStop.

Особый интерес представляет Restore. Он вызывается, когда пользователь возвращается из PiP в приложение. В этом методе нужно восстановить UI плеера и вызвать completionHandler(true).

func pictureInPictureController(
_ pictureInPictureController: AVPictureInPictureController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
self.playerView.isHidden = false
completionHandler(true)
}

Для автоматического запуска PiP при свайпе на главный экран можно использовать флаг canStartPictureInPictureAutomaticallyFromInline.

Как устроен PiP под капотом:


Если запустить приложение с PiP и посмотреть на таймлайн в Xcode Instruments, видно, что больше всего событий приходится на процессы AVKit и Pegasus.

Pegasus - системный фреймворк для работы с Picture-in-Picture. Архитектурная цепочка выглядит так:

AVPictureInPictureController -> AVPictureInPicturePlatformAdapter -> PGPictureInPictureProxy

AVPictureInPictureController не рисует системное окно сам. Он передает команду адаптеру, который обращается к PGPictureInPictureProxy. Именно этот класс владеет свойством UIWindow для окна PiP.

Отслеживание действий пользователя:


Когда пользователь ставит видео на паузу или перематывает его через системные контролы, делегат не предоставляет методов для этих событий.

В iOS 18+ появился механизм AVMetrics. Можно подписаться на асинхронный поток событий перемотки:

if let stream = item.metrics(forType: AVMetricPlayerItemSeekEvent.self) {
Task {
for try await event in stream {
print("Пользователь перемотал видео")
}
}
}

На более старых версиях iOS используется KVO или Combine. Нужно наблюдать за свойством timeControlStatus у AVPlayer. Когда пользователь нажимает паузу в окне PiP, статус меняется на .paused.

Переключение контента:


Есть два сценария:

  • Смена текущего AVPlayerItem. Если меняется только контент, достаточно заменить item у текущего плеера. PiP продолжит работать плавно.

    let nextItem = AVPlayerItem(url: nextEpisodeURL)
    player.replaceCurrentItem(with: nextItem)

  • Пересоздание плеера. Если архитектура требует нового AVPlayer и нового AVPlayerLayer, можно обновить contentSource у контроллера. PiP переключится без разрывов.

    pipController.contentSource = .init(playerLayer: newPlayerLayer)


Если посмотреть на логи в Instruments в момент смены contentSource, видно, что метаданные контента меняются, а адреса объектов PGPictureInPictureProxy и PGPlaybackState остаются прежними. Это значит, что система переиспользует существующий прокси и состояние.

Жизненный цикл приложения:


Окно PiP привязано к процессу приложения.

Если пользователь блокирует экран, видео продолжит играть только при корректно настроенной аудиосессии.

Если пользователь смахивает приложение из многозадачности, процесс уничтожается и окно PiP мгновенно закрывается. Apple не позволяет PiP работать без хост-приложения.

iOS может принудительно приостановить фоновый плеер, если приложение долго не выводит изображение. В этом случае делегат уведомит через DidStop, и нужно корректно очистить ресурсы.

Вывод:


Picture-in-Picture - мощный инструмент, который улучшает пользовательский опыт. Но для стабильной работы критически важно правильно настроить аудиосессию и Background Modes, грамотно управлять памятью контроллера и использовать contentSource для бесшовного переключения видео.

Подкапотный механизм сложнее, чем кажется. AVKit взаимодействует с системным фреймворком Pegasus, который управляет окном PiP. Понимание этой архитектуры помогает избегать типичных ошибок, а работу сделать стабильной и надежной.
01.10.2026 33 655