I have a controller with a AVPlayer in collection view cell. When orientation changes to Landscape, the player should get FullScreen.
For this I am presenting an AVPlayerController with same instance of player in Collection View Cell. The video works fine when it is rotated in playing mode.
However, when video is paused and I change orientation to Landscape, the frame at current moment changes i.e video moves forward.
I have tested, even when the orientation is kept same, when player is passed, the duration skips few seconds.
Here is the code:
In ViewController where cell is present.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
guard let videoCell = contentGalleryController.curatorVideoCell else {return}
if UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight {
let player = videoCell.getVideoPlayer
playerViewController = (storyboard!.instantiateViewController(withIdentifier: "FullScreenPlayerController") as! FullScreenPlayerController)
playerViewController?.player = player
playerViewController?.didPlayedToEnd = videoCell.isVideoFinishedPlaying ?? false
playerViewController?.isMuteTurnedOn = player.isMuted
let wasVideoPlaying: Bool = player.isPlaying
present(playerViewController!, animated: false){
if wasVideoPlaying {
player.play()
}
}
}
else {
videoCell._setMuteIcon()
videoCell._setPlayPauseIcon()
playerViewController?.dismiss(animated: false, completion: nil)
}
}
In FullscreenPlayer View Controller
override func viewDidLoad() {
super.viewDidLoad()
setPlayPauseIcon()
setMuteIcon()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(customControlViewTapped))
customView.addGestureRecognizer(tapGesture)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !view.subviews.contains(customView) {
customView.frame = view.bounds
view.addSubview(customView)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidPlayToEnd), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
I am doing nothing else in controller.
Screenshots:
Portrait
LANDSCAPE
When orientation changes, video moves forward even on pause state.
Thanks for help in advance.
You should try capturing the currentTime and then use the seek(to time: CMTime) method on the player to start at that exact time. I am not sure exactly why this is happening to you, but I think this would get you the result you are looking for.
Related
Okay So I have one view controller that is an AVPlayer. I want that controller to be able to rotate to landscape mode and portrait freely
import Foundation
import UIKit
import AVFoundation
import AVKit
class EventPromoVideoPlayer: UIViewController {
public var eventKey = ""
override var prefersStatusBarHidden: Bool {
return true
}
//URL of promo video that is about to be played
private var videoURL: URL
// Allows you to play the actual mp4 or video
var player: AVPlayer?
// Allows you to display the video content of a AVPlayer
var playerController : AVPlayerViewController?
init(videoURL: URL) {
self.videoURL = videoURL
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.gray
let downSwipe = UISwipeGestureRecognizer(target: self, action: #selector(swipeAction(_:)))
downSwipe.direction = .down
view.addGestureRecognizer(downSwipe)
//Setting the video url of the AVPlayer
player = AVPlayer(url: videoURL)
playerController = AVPlayerViewController()
guard player != nil && playerController != nil else {
return
}
playerController!.showsPlaybackControls = false
// Setting AVPlayer to the player property of AVPlayerViewController
playerController!.player = player!
self.addChildViewController(playerController!)
self.view.addSubview(playerController!.view)
playerController!.view.frame = view.frame
// Added an observer for when the video stops playing so it can be on a continuous loop
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.player!.currentItem)
//TODO: Need to fix frame of x and y
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
player?.play()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = true
tabBarController?.tabBar.isHidden = true
}
override var supportedInterfaceOrientations:UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.all
}
// Allows the video to keep playing on a loop
#objc fileprivate func playerItemDidReachEnd(_ notification: Notification) {
if self.player != nil {
self.player!.seek(to: kCMTimeZero)
self.player!.play()
}
}
#objc func cancel() {
dismiss(animated: true, completion: nil)
}
#objc func swipeAction(_ swipe: UIGestureRecognizer){
if let swipeGesture = swipe as? UISwipeGestureRecognizer {
switch swipeGesture.direction {
case UISwipeGestureRecognizerDirection.right:
print("Swiped right")
break
case UISwipeGestureRecognizerDirection.down:
print("Swiped Down")
dismiss(animated: true, completion: nil)
break
case UISwipeGestureRecognizerDirection.left:
print("Swiped left")
break
case UISwipeGestureRecognizerDirection.up:
print("Swiped up")
break
default:
break
}
}
}
}
When I dismiss the screen I still need the previous controller to be in portrait. This shouldnt be a problem seeing as portrait mode is locked in as the only orientation in my settings and I want to keep it that way. However I want this one screen to be able to move freely. Any ideas and hints would be greatly appreciated.
The app delegate method does not work for me.
Neither does overriding shouldAutoRotate or supportedInterfaceOrientations in the function
You can use the below method in app delegate.
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let rootViewController = UIApplication.topViewController() {
if (rootViewController.responds(to: Selector(("canRotate")))) || String(describing: type(of: rootViewController)) == "AVFullScreenViewController" {
// Unlock landscape view orientations for this view controller
return .allButUpsideDown;
}
}
// Only allow portrait (standard behaviour)
return .portrait
}
Add the below method in view controller where you want to display as landscape.
func canRotate() -> Void {}
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
Im currently using 'AVKit' and 'AVFoundation' to enable the video playing through my app but I can only done by starting the video by tapping a button. But I am currently facing a problem of trying to making an automatic function like playing the video footage right after we start an app on an iOS device.
override func viewDidLoad()
{
super.viewDidLoad()
self.playingVideo()
self.nowPlaying(self)
}
#IBAction func nowPlaying(_ sender: Any)
{
self.present(self.playerController, animated: true, completion:{
self.playerController.player?.play()
})
after I compiled it, the system printed out:
Warning: Attempt to present on whose view is not in the window hierarchy!
Please try following working code.
import AVFoundation
import UIKit
class SomeViewController: UIViewController {
func openVideo() {
let playerController = SomeMediaPlayer()
playerController.audioURL = URL(string: "Some audio/video url string")
self.present(playerController, animated: true, completion: nil)
}
}
class SomeMediaPlayer: UIViewController {
public var audioURL:URL!
private var player = AVPlayer()
private var playerLayer: AVPlayerLayer!
override func viewDidLoad() {
super.viewDidLoad()
self.playerLayer = AVPlayerLayer(player: self.player)
self.view.layer.insertSublayer(self.playerLayer, at: 0)
let playerItem = AVPlayerItem(url: self.audioURL)
self.player.replaceCurrentItem(with: playerItem)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.player.play()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.playerLayer.frame = self.view.bounds
}
// Force the view into landscape mode if you need to
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
get {
return .landscape
}
}
}
func SetDeviceFontSizes()
{
...
imgView.frame.size.width = 375
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
SetDeviceFontSizes()
}
override func viewWillLayoutSubviews() {
SetDeviceFontSizes()
}
override func viewDidLoad() {
super.viewDidLoad()
SetDeviceFontSizes()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
SetDeviceFontSizes()
}
I want to set sizes for labels and images (for old devices these sizes will be small).
But when I change orientation of device, the sizes return to default values in spite of function SetDeviceFontSizes().
How can I set frame sizes after change orientation?
var m_CurrentOrientation = UIDeviceOrientation()
add this line in your viewdidload()
UIDevice.currentDevice().beginGeneratingDeviceOrientationNotifications()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.deviceOrientationDidChange), name: .DidChangeNotification, object: nil)
perform your task in if condition
func OrientationDidChange(notification: NSNotification) {
var Orientation = UIDevice.currentDevice().orientation
if Orientation == .LandscapeLeft || Orientation == .LandscapeRight {
}
else if Orientation == .Portrait {
}
}
i'm not sure this will solve your problem, but you should call super in viewWillLayoutSubviews and viewWillTransition
You can use UITraitCollection. Refer to this link
How to translate this objective c traitCollection to swift?
Do not call anything in viewWillLayoutSubviews() function.
Hope it helps you.
A storyboard has tow ViewControllers-s, one for Portrait orientation (VCPort for short), another for landscape (VCLand). I want the application to switch automatically to correct layout when the device is rotated.
For that purpose I have two segues portToLand from VSPort to VCLand and LandToPort in opposite direction. Then I override willRotateToScreenOrientation as the following.
For VCPort class:
override func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval)
{
if toInterfaceOrientation.isLandscape {
switchViewController()
}
}
func switchViewController() {
performSegueWithIdentifier("portToLand", sender: self)
}
For VCLand class I have isPortrait instead of isLandscape and "landToPort" instead of "portToLand".
Everything works OK if the storyboard starts in Portrait orientation. When the storyboard starts while the device is in Landscape (e.g. with IPad Retina) VCPort still gets the control as the first ViewController in chain and willRotateToScreen doesn't come. To make the application switch to Landscape layout in this case I place the following code in viewDidLoad for VCPort class:
override func viewDidLoad() {
super.viewDidLoad()
if UIApplication.sharedApplication().statusBarOrientation.isLandscape {
switchViewController()
}
}
Tracing with debugger show thats performSegueWithIdentifier is called, but VCLand doesn't get the control, the application still shows VCPort layout! In assumption that I do it too early, I tried viewWillAppear instead of viewDidLoad - no difference.
Thanks for any comments.
Just put a short delay before switching views. And moved the code to viewWillAppear, since the orientation may change while app is in pause:
var timer : NSTimer?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let deviceOrientation = UIDevice.currentDevice().orientation
var isLandscape : Bool;
if deviceOrientation == UIDeviceOrientation.Unknown {
isLandscape = UIApplication.sharedApplication().statusBarOrientation.isLandscape
} else {
isLandscape = deviceOrientation.isLandscape
}
if isLandscape {
self.view.hidden = true
timer = NSTimer.scheduledTimerWithTimeInterval(0.05, target: self, selector: Selector("switchViewController"), userInfo: nil, repeats: false)
} else {
self.view.hidden = false
}
}
override func viewWillDisappear(animated: Bool) {
if (timer != nil) {
timer!.invalidate()
timer = nil
}
}
I'm porting my iPad app to iOS8 and Swift.
In portrait, i use a root UIViewController and when the de device is rotated to landscape, I segue to another UIViewController. I've come up with 2 solutions, one based on UIDevice notifications and the other on willRotateToInterfaceRotation. I alway try to stay away from the Observer pattern, it's just a habit of me.
The Observer works fine, but the override in both UIViewController of
func willRotateToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval)
looks cleaner to my eyes ;)
But now in iOS8 that function is deprecated and I should use
func viewWillTransitionToSize(_ size: CGSize,
withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator)
But I don't have any idea how I can work with that to get the same result.
This is the rootViewController:
override func willRotateToInterfaceOrientation(
toInterfaceOrientation: UIInterfaceOrientation,
duration: NSTimeInterval) {
if (toInterfaceOrientation == UIInterfaceOrientation.LandscapeLeft ||
toInterfaceOrientation == UIInterfaceOrientation.LandscapeRight) {
self.performSegueWithIdentifier("toLandscape", sender: self)
}
}
and the UIViewControllerLanscape:
override func willRotateToInterfaceOrientation(
toInterfaceOrientation: UIInterfaceOrientation,
duration: NSTimeInterval) {
if (toInterfaceOrientation == UIInterfaceOrientation.Portrait ||
toInterfaceOrientation == UIInterfaceOrientation.PortraitUpsideDown) {
self.presentingViewController?.dismissViewControllerAnimated(true,
completion: nil)
}
}
I don't like to use deprecated functions, so I'm in doubt what to do...go for the observer or what??
This is the code I use when (only) the root UIViewController is an observer of the UIDeviceOrientationDidChangeNotification:
override func awakeFromNib() {
let dev = UIDevice.currentDevice()
dev.beginGeneratingDeviceOrientationNotifications()
let nc = NSNotificationCenter.defaultCenter()
nc.addObserver(self, selector: "orientationChanged:", name: UIDeviceOrientationDidChangeNotification, object: dev)
}
func orientationChanged(note: NSNotification) -> () {
if let uidevice: UIDevice = note.object? as? UIDevice {
switch uidevice.orientation {
case .LandscapeLeft, .LandscapeRight:
self.performSegueWithIdentifier("toLandscape", sender: self)
default:
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
I really like your ideas on how to make a solution for these deprecated functions. It's a total mystery to me....or maybe the Observer is the better solution now. Please share your ideas.
Regards,
jr00n