관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - Swift] PIP(Picture In Picture) 사용 방법 (AVPictureInPictureControllerDelegate, 유튜브 PIP) 본문

iOS 응용 (swift)

[iOS - Swift] PIP(Picture In Picture) 사용 방법 (AVPictureInPictureControllerDelegate, 유튜브 PIP)

jake-kim 2022. 12. 6. 22:39

구현된 pip 모드

PIP 모드 구현 방법

  • iOS 14+ 
  • Target -> Signing & Capabilities -> Capability -> Background 추가
  • 아래 그림과 같이 Modes > Audio, AirPlay, and Picture in Picture 체크

  • ViewController에 필요한 AVKit, AVFoundation 임포트
import AVKit
import AVFoundation
  • 필요한 UI 선언
    • playerLayer: 비디오 화면이 들어갈 layer
    • player: URL을 입력 받아서 플레이하는 인스턴스
    • pipController: PIP 기능이 있는 컨트롤러이고, playerLayer를 주입해주면 바로 사용이 가능
class ViewController: UIViewController {
  private let playerLayer = AVPlayerLayer()
  private let player = AVPlayer()
  private var pipController: AVPictureInPictureController?
  private let button: UIButton = {
    let button = UIButton(type: .system)
    button.setTitle("pip 활성화", for: .normal)
    button.translatesAutoresizingMaskIntoConstraints = false
    return button
  }()
  
}
  • pip 사용
    • AVPlayerItem을 가지고 player에 mp4파일을 준 후 재생
    • playerLayer의 player를 위 player로 지정
    • AVPictureInPictureController에 playerLayer를 주입하면 완성
    • (델리게이트는 아래에서 계속)
  override func viewDidLoad() {
    super.viewDidLoad()
    
    setPip()
  }
  
  private func setPip() {
    guard let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4") else { return }
    
    let asset = AVPlayerItem(url: url)
    player.replaceCurrentItem(with: asset)
    playerLayer.player = player
    playerLayer.videoGravity = .resizeAspect
    view.layer.addSublayer(playerLayer)
    player.play()
    
    guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
    pipController = AVPictureInPictureController(playerLayer: playerLayer)
    pipController?.delegate = self
  }
  • playerLayer의 frame은 런타임시에 정해진 view의 크기만큼 넣어줄 것이므로 viewDidLayoutSubviews()에서 할당
  override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    playerLayer.frame = view.bounds
  }
  • PIP모드 활성화/끄기
    • AVPictureInPictureController의 메소드 사용
    • 끄기 - stopPictureInPicture()
    • 켜기 - startPictureInPicture()
  @objc func tapButton() {
    guard let isActive = pipController?.isPictureInPictureActive else { return }
    isActive ? pipController?.stopPictureInPicture() : pipController?.startPictureInPicture()
    isActive ? button.setTitle("비활성화", for: .normal) : button.setTitle("활성화", for: .normal)
  }
  • 델리게이트 AVPictureInPictureControllerDelegate
    • 위에서 설정한 pipController?.delegate = self 델리게이트
    • pip 버튼을 눌렀을때 willStart, didStart
    • 중지할때 willStop, didStart
    • 작아진 pip화면을 다시 키울때 restore
extension ViewController: AVPictureInPictureControllerDelegate {
  func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
    print("willStart")
  }
  func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
    print("didStart")
  }
  func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
    print("error")
  }
  func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
    print("willStop")
  }
  func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
    print("didStop")
  }
  func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
    print("restore")
  }
}

구현된 pip 모드

* 전체 코드: https://github.com/JK0369/ExPip

* 참고

https://jplanet89.tistory.com/entry/PictureInPicture

https://developer.apple.com/documentation/avkit/adopting_picture_in_picture_in_a_custom_player

Comments