The documentation for AVPictureInPictureVideoCallController suggests that it works with an AVCaptureVideoPreviewLayer in addition to the AVSampleBufferDisplayLayer, however I'm having a hard time getting it to actually work. Here's my sample code:
class PIPPreviewView: UIView {
override class var layerClass: AnyClass {
get { return AVCaptureVideoPreviewLayer.self }
}
var previewLayer: AVCaptureVideoPreviewLayer {
return layer as! AVCaptureVideoPreviewLayer
}
var session: AVCaptureSession? {
get { previewLayer.session }
set { previewLayer.session = newValue }
}
}
final class PreviewViewController: UIViewController, AVPictureInPictureControllerDelegate {
private let captureSession = AVCaptureSession()
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
do {
let videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
captureSession.addInput(videoInput)
} catch {
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
previewLayer.connection?.videoOrientation = .landscapeLeft
view.layer.addSublayer(previewLayer)
let pipview = PIPPreviewView()
pipview.session = self.captureSession
let pipvc = AVPictureInPictureVideoCallViewController()
pipvc.preferredContentSize = CGSize(width: 1080, height: 1920)
pipvc.view.addSubview(pipview)
let pipcs = AVPictureInPictureController.ContentSource(activeVideoCallSourceView: pipview, contentViewController: pipvc)
let pipc = AVPictureInPictureController(contentSource: pipcs)
pipc.canStartPictureInPictureAutomaticallyFromInline = true
pipc.delegate = self
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
print("starting pip")
pipc.startPictureInPicture()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if (!captureSession.isRunning) {
DispatchQueue.global(qos: .background).async {
self.captureSession.startRunning()
}
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if (captureSession.isRunning) {
captureSession.stopRunning()
}
}
override var prefersStatusBarHidden: Bool {
return true
}
}
When I run this, the preview layer in app is black and the application doesn't trigger PIP. Has anyone gotten this to work properly?
Hello,
It does work for AVCaptureVideoPreviewLayer, your example has two different AVCaptureVideoPreviewLayers, so perhaps that is causing some issues. In any case, I will just post a minimal working example here, I recommend that you compare against this example to see what may be different in your implementation:
PreviewView.swift
import UIKit
import AVKit
class PreviewView: UIView {
override class var layerClass: AnyClass {
AVCaptureVideoPreviewLayer.self
}
var previewLayer: AVCaptureVideoPreviewLayer {
layer as! AVCaptureVideoPreviewLayer
}
init(_ session: AVCaptureSession) {
super.init(frame: .zero)
previewLayer.session = session
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension AVPictureInPictureVideoCallViewController {
convenience init(_ previewView: PreviewView, preferredContentSize: CGSize) {
// Initialize.
self.init()
// Set the preferredContentSize.
self.preferredContentSize = preferredContentSize
// Configure the PreviewView.
previewView.translatesAutoresizingMaskIntoConstraints = false
previewView.frame = self.view.frame
self.view.addSubview(previewView)
NSLayoutConstraint.activate([
previewView.topAnchor.constraint(equalTo: self.view.topAnchor),
previewView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
previewView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
previewView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)
])
}
}
ViewController.swift
import UIKit
import AVFoundation
import AVKit
class ViewController: UIViewController {
let captureSession = AVCaptureSession()
let captureSessionQueue = DispatchQueue(label: "Capture Session Queue")
var pipVideoCallViewController: AVPictureInPictureVideoCallViewController!
var pipController: AVPictureInPictureController!
override func viewDidLoad() {
super.viewDidLoad()
let previewView = PreviewView(captureSession)
pipVideoCallViewController = .init(previewView,
preferredContentSize: CGSize(width: 1080, height: 1920))
let pipContentSource = AVPictureInPictureController.ContentSource(
activeVideoCallSourceView: view,
contentViewController: pipVideoCallViewController)
pipController = AVPictureInPictureController(contentSource: pipContentSource)
pipController.delegate = self
pipController.canStartPictureInPictureAutomaticallyFromInline = true
startSession()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if !pipController.isPictureInPictureActive {
pipController.startPictureInPicture()
}
}
private func startSession() {
captureSessionQueue.async { [unowned self] in
let device = AVCaptureDevice.default(for: .video)!
captureSession.addInput(try! AVCaptureDeviceInput(device: device))
captureSession.sessionPreset = .hd1920x1080
captureSession.isMultitaskingCameraAccessEnabled = captureSession.isMultitaskingCameraAccessSupported
captureSession.startRunning()
}
}
}
extension ViewController: AVPictureInPictureControllerDelegate {
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
print(error.localizedDescription)
}
}