class WaterbusViewController: FlutterViewController {
static let shared = WaterbusViewController()
// MARK: Public static variables
static var pipController: AVPictureInPictureController?
static var pipContentSource: Any?
static var pipVideoCallViewController: Any?
// MARK: Private variables
private var pictureInPictureView: PictureInPictureView = PictureInPictureView()
open override func viewDidLoad() {
// get the flutter engine for the view
let flutterEngine: FlutterEngine! = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
addFlutterView(with: flutterEngine)
// configuration pip view controller
preparePictureInPicture()
func preparePictureInPicture() {
if #available(iOS 15.0, *) {
WaterbusViewController.pipVideoCallViewController = AVPictureInPictureVideoCallViewController()
(WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).preferredContentSize = CGSize(width: Sizer.WIDTH_OF_PIP, height: Sizer.HEIGHT_OF_PIP)
(WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.clipsToBounds = true
WaterbusViewController.pipContentSource = AVPictureInPictureController.ContentSource(
activeVideoCallSourceView: self.view,
contentViewController: (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController)
func configurationPictureInPicture(result: @escaping FlutterResult, peerConnectionId: String, remoteStreamId: String, isRemoteCameraEnable: Bool, myAvatar: String, remoteAvatar: String, remoteName: String) {
if #available(iOS 15.0, *) {
if (WaterbusViewController.pipContentSource != nil) {
WaterbusViewController.pipController = AVPictureInPictureController(contentSource: WaterbusViewController.pipContentSource as! AVPictureInPictureController.ContentSource)
WaterbusViewController.pipController?.canStartPictureInPictureAutomaticallyFromInline = true
WaterbusViewController.pipController?.delegate = self
let frameOfPiP = (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.frame
pictureInPictureView = PictureInPictureView(frame: frameOfPiP)
pictureInPictureView.contentMode = .scaleAspectFit
pictureInPictureView.initParameters(peerConnectionId: peerConnectionId, remoteStreamId: remoteStreamId, isRemoteCameraEnable: isRemoteCameraEnable, myAvatar: myAvatar, remoteAvatar: remoteAvatar, remoteName: remoteName)
(WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.addSubview(pictureInPictureView)
func addConstraintLayout() {
if #available(iOS 15.0, *) {
pictureInPictureView.translatesAutoresizingMaskIntoConstraints = false
pictureInPictureView.leadingAnchor.constraint(equalTo: (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.leadingAnchor),
pictureInPictureView.trailingAnchor.constraint(equalTo: (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.trailingAnchor),
pictureInPictureView.topAnchor.constraint(equalTo: (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.topAnchor),
pictureInPictureView.bottomAnchor.constraint(equalTo: (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.bottomAnchor)
(WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.addConstraints(constraints)
pictureInPictureView.bounds = (WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.frame
func updatePictureInPictureView(_ result: @escaping FlutterResult, peerConnectionId: String, remoteStreamId: String, isRemoteCameraEnable: Bool, remoteAvatar: String, remoteName: String) {
pictureInPictureView.setRemoteInfo(peerConnectionId: peerConnectionId, remoteStreamId: remoteStreamId, isRemoteCameraEnable: isRemoteCameraEnable, remoteAvatar: remoteAvatar, remoteName: remoteName)
func updateStateUserView(_ result: @escaping FlutterResult, isRemoteCameraEnable: Bool) {
pictureInPictureView.updateStateValue(isRemoteCameraEnable: isRemoteCameraEnable)
func disposePictureInPicture() {
pictureInPictureView.disposeVideoView()
if #available(iOS 15.0, *) {
(WaterbusViewController.pipVideoCallViewController as! AVPictureInPictureVideoCallViewController).view.removeAllSubviews()
if (WaterbusViewController.pipController == nil) {
WaterbusViewController.pipController = nil
func stopPictureInPicture() {
if #available(iOS 15.0, *) {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
WaterbusViewController.pipController?.stopPictureInPicture()