toemat
toemat

Reputation: 180

AVPlayerViewController full screen rotation behavior in portrait only app

When embedding an AVPlayerViewController in a portrait only iOS app it seems the app can get stuck in a weird layout when the player exits full screen if the video is full-screened while the device is held in a landscape orientation.

Is this a bug or am I doing something incorrectly?

Here's how to reproduce with a clean project using Xcode 9.4.1, swift 4, iOS 11.4, simulator or physical device.

ViewController.swift

override func viewDidLoad() {
    super.viewDidLoad()

    //Create the player and add as child view controller
    let playerVC = AVPlayerViewController()
    self.addChildViewController(playerVC)

    //Place player's view in self
    playerVC.view.frame = CGRect(x: 10, y: 40, width: 355, height: 200)
    self.view.addSubview(playerVC.view)

    //Load example video
    playerVC.player = AVPlayer(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)
}

How it works normally:

How it breaks:

Upvotes: 6

Views: 8153

Answers (3)

Sri Vicky
Sri Vicky

Reputation: 1

I tried to achieve using willTransitionToTraitCollection method. I have a viewController which has reference of AVPlayerViewController and a AVPlayer and inline playerView 1.Lock the orientation to allButUpsideDown 2. On load, load the AVPlayerViewController as a child and snap it to PlayerView 3. And override willTransitionToNewTraitCollection method


override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) {
    super.willTransition(to: newCollection, with: coordinator)
    coordinator.animate(alongsideTransition: { [unowned self] _ in

      if newCollection.verticalSizeClass == .compact {

        self.videoPlayerController.willMove(toParent: nil)
        self.videoPlayerController.view.removeFromSuperview()
        self.videoPlayerController.removeFromParent()

        DispatchQueue.main.async {
          self.present(self.videoPlayerController,
                       animated: false) {
                        // Do some stuff if needed
          }
        }
      }
      else if newCollection.verticalSizeClass == .regular {
        self.dismiss(animated: false) {
          DispatchQueue.main.async {

            self.videoPlayerController.view.frame = self.playerView.bounds
            self.playerView.addSubview(self.videoPlayerController.view)
          }
        }
      }
    }) { _ in
        // Lock the orientation to allButUpSideDown after animation.
    }
  }

Upvotes: 0

Ivan Georgiev
Ivan Georgiev

Reputation: 192

When you leave player full screen you enter in viewWillApper of your View Controller. So in viewWillAppear try to set your window frame to be equal to your screen bounds.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    appDelegate.window?.rootViewController?.view.frame = UIScreen.main.bounds
}

Upvotes: 4

Nabil
Nabil

Reputation: 51

You can identify when the player become fullscreen and force the portrait or landscape for your view controller.

It is not the best solution, but at least avoids the broken UI:

    import AVFoundation
    import AVKit

    var isPlayerFullscreen: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()

        //Create the player and add as child view controller
        let playerVC = AVPlayerViewController()
        self.addChildViewController(playerVC)

        //Place player's view in self
        playerVC.view.frame = CGRect(x: 10, y: 40, width: 355, height: 200)
        self.view.addSubview(playerVC.view)

        //Load example video
        playerVC.player = AVPlayer(url: URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")!)

        //Add an observer for playerVC object
        playerVC.addObserver(self, forKeyPath: "videoBounds", options: NSKeyValueObservingOptions.new, context: nil)
    }

    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
    {
        if keyPath == "videoBounds", let rect = change?[.newKey] as? NSValue
        {
            let playerRect: CGRect = rect.cgRectValue
            if playerRect.size.height <= 200 {
                print("Video not in full screen")                
                UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
isPlayerFullscreen = false
            } else {
                print("Video in full screen")
                isPlayerFullscreen = true
            }
        }
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        return isPlayerFullscreen ? UIInterfaceOrientationMask.landscape: UIInterfaceOrientationMask.portrait
    }

    override var shouldAutorotate: Bool {
        return true
    }

Upvotes: 0

Related Questions