Search code examples
swiftavkitpicture-in-picture

How can I hide the default close button of AVPictureInPictureController?


Implemented pip function using AVPictureInPictureController. I hid the player and time search, but failed to hide the last close button and return button.

The buttons I want to hide are these buttons. enter image description here

The one I referenced is the following github.

https://github.com/uakihir0/UIPiPDemo

The source I used to declare AVPictureInPictureController is as follows.

import UIKit
import AVKit

class ViewController: UIViewController {

@IBOutlet weak var labelTest: UILabel!

private var pipUi = PipUi()
private var _pipController: AVPictureInPictureController?
private let _bufferDisplayLayer = AVSampleBufferDisplayLayer()
private var _pipPossibleObservation: NSKeyValueObservation?
private var _observer: NSObjectProtocol?

override func viewDidLoad() {
    super.viewDidLoad()
    
    let margin = ((view.bounds.width - 200) / 2)
    let videoContainerView = UIView()
    videoContainerView.frame = .init(x: margin, y: 200, width: 200, height: 30)
    videoContainerView.backgroundColor = UIColor.blue
    self.view.addSubview(videoContainerView)

    let bufferDisplayLayer = pipUi.bufferDisplayLayer
    bufferDisplayLayer.frame = videoContainerView.bounds
    bufferDisplayLayer.backgroundColor = UIColor.brown.cgColor
    bufferDisplayLayer.videoGravity = .resizeAspect
    videoContainerView.layer.addSublayer(bufferDisplayLayer)
    
    _bufferDisplayLayer.flush()

    pipUi.start()
    
    labelTest.setOnTouchListener(self, action: #selector(toggle))

    DispatchQueue.main.async {
        self.start()
    }
}

func start() {
    
    // PinP をサポートしているデバイスかどうかを確認
    if AVPictureInPictureController.isPictureInPictureSupported() {
        
        // AVPictureInPictureController の生成
        _pipController = AVPictureInPictureController(
            contentSource: .init(
                sampleBufferDisplayLayer:
                    pipUi.bufferDisplayLayer,
                playbackDelegate: self))
        _pipController?.delegate = self
        
        _pipPossibleObservation = _pipController?.observe(
            \AVPictureInPictureController.isPictureInPicturePossible,
             options: [.initial, .new],
             changeHandler: { [weak self] _, changed in
                 DispatchQueue.main.async {
                     if changed.newValue == true {
                         //self?.start()
                     }
                 }
             })
    }
    
    if #available(iOS 14.0, *) {
        //Hide the seekbar
        _pipController?.requiresLinearPlayback = true

    } else {
        // Fallback on earlier versions
    }
    
    //Hide the play/pause controls
    _pipController?.setValue(true, forKey: "controlsStyle")

    
}

@objc func toggle() {
    guard let _pipController = _pipController else { return }
    if !_pipController.isPictureInPictureActive {
        print("start")
        _pipController.startPictureInPicture()
    } else {
        print("stop")
        _pipController.stopPictureInPicture()
    }
}
}


extension ViewController: AVPictureInPictureControllerDelegate {

func pictureInPictureController(
    _ pictureInPictureController: AVPictureInPictureController,
    failedToStartPictureInPictureWithError error: Error
) {
    print("\(#function)")
    print("pip error: \(error)")
}

func pictureInPictureControllerWillStartPictureInPicture(
    _ pictureInPictureController: AVPictureInPictureController
) {
    print("\(#function)")
}

func pictureInPictureControllerWillStopPictureInPicture(
    _ pictureInPictureController: AVPictureInPictureController
) {
    print("\(#function)")
}
}

extension ViewController: AVPictureInPictureSampleBufferPlaybackDelegate {

func pictureInPictureController(
    _ pictureInPictureController: AVPictureInPictureController,
    setPlaying playing: Bool
) {
    print("\(#function)")
}

func pictureInPictureControllerTimeRangeForPlayback(
    _ pictureInPictureController: AVPictureInPictureController
) -> CMTimeRange {
    print("\(#function)")
    return CMTimeRange(start: .negativeInfinity, duration: .positiveInfinity)
}

func pictureInPictureControllerIsPlaybackPaused(
    _ pictureInPictureController: AVPictureInPictureController
) -> Bool {
    print("\(#function)")
    return false
}

func pictureInPictureController(
    _ pictureInPictureController: AVPictureInPictureController,
    didTransitionToRenderSize newRenderSize: CMVideoDimensions
) {
    print("\(#function)")
    print(newRenderSize)
}

func pictureInPictureController(
    _ pictureInPictureController: AVPictureInPictureController,
    skipByInterval skipInterval: CMTime,
    completion completionHandler: @escaping () -> Void
) {
    print("\(#function)")
    completionHandler()
}

}

I want to hide the close button to customize it. please help.


Solution

  • I doubdt you can totally hide "close" and "return to app" buttons, becasue they are essential to PiP use and there is no API to hide them (as far as I know) . However if you tap into video screen, all buttons are temporarily hidden. Also those button are hidden automatically after 2 seconds of playback. Also Apple talkes about those buttons as mean of interaction with PiP in following support guide