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
}
Related
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.
I have a custom view:
class MediaPlayerView: UIView {
var mediaURL: URL? {
didSet {
determineMediaType()
}
}
let videoExtensions = ["mov"]
override init (frame : CGRect) {
super.init(frame : frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func determineMediaType() {
let url = self.mediaURL
let pathExtention = url?.pathExtension
if videoExtensions.contains(pathExtention!) {
print("Movie URL: \(String(describing: url))")
setupVideo(url: url!)
} else {
print("Image URL: \(String(describing: url))")
setupImage(url: url!)
}
}
func setupVideo(url: URL) {
let playButton = UIImage(named: "Play Triangle")
let playButtonView = UIImageView(image: playButton!)
let singleTap = UITapGestureRecognizer(target: self, action: #selector(tapDetected))
playButtonView.addGestureRecognizer(singleTap)
playButtonView.center = self.center
playButtonView.frame.size.width = self.frame.size.width/5
playButtonView.frame.size.height = self.frame.size.height/5
playButtonView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
playButtonView.isUserInteractionEnabled = true
self.addSubview(playButtonView)
}
#objc func tapDetected() {
print("tap!")
let player = AVPlayer(url: self.mediaURL!)
let controller = AVPlayerViewController()
controller.player = player
self.window?.rootViewController?.present(controller, animated: true) {
player.play()
}
}
func setupImage(url: URL) {
let imageView = UIImageView()
imageView.frame = self.bounds
imageView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
self.addSubview(imageView)
imageView.kf.setImage(with: url)
}
}
However when I click the play button, I get the following error:
Warning: Attempt to present <AVPlayerViewController: 0x7fe8b200b000> on <SweatNet.MainTabBarController: 0x7fe8b4816400> whose view is not in the window hierarchy!
It comes on this line: self.window?.rootViewController?.present. I think It is getting confused by me calling the rootViewController (which seems to be SweatNet.MainTabBarController). I want to it to call SweatNet.TagViewController. This is the one which contains the cell which contains the custom MediaPlayerView, but I don't understand how to get a reference to this.
Try this:
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
Write your method as:
#objc func tapDetected() {
print("tap!")
let player = AVPlayer(url: self.mediaURL!)
let controller = AVPlayerViewController()
controller.player = player
topViewController().present(controller, animated: true) {
player.play()
}
}
This will return you top vc in hierarchy. Hope it helps!
I would be interested in some feedback on this answer as I think its an example of me thinking about the iOS system the wrong way. What I eventually realized was that I was trying to put view controller code inside of a custom view. View controller code being the code to launch the AVPlayerViewController. Rather I should put this logic in my view controller.
What I did which works is use the collectionView delegate method didSelectItemAt indexPath: IndexPath inside of the view controller where the view controller logic for the collection view is held like so:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == self.collectionView {
let post = posts[indexPath.row]
if post.isVideo == true {
self.performSegue(withIdentifier: "playVideo", sender: nil)
} else {
print("is image. might go full screen one day here")
}
}
This works and I believe it is better than writing a custom aspect of the view to launch the AVPlayerViewController. But it also could be that both approaches work - I really don't know enough about iOS development to make that call. Any comments are welcome.
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
}
}
}
I created a simple ViewController class that plays a livestream video using AVPlayer. The livestream is loaded in viewDidLoad and when the user presses play, it plays the livestream, simple enough.
The parent VC is set for portrait orientation only.
I have another button that segues to another ViewController using a custom segue.
How do I pass the AVPlayer and its "state" to the destination ViewController?......i.e. if the AVPlayer is currently playing, it should maintain its state when it's passed from parent VC to destination VC.
FYI: I'm not using prepareForSegue.
Here's the complete simple code:
ViewController.swift:
class ViewController: UIViewController {
#IBOutlet var playerView: UIView!
var player = AVPlayer()
var avPlayerLayer: AVPlayerLayer!
override func viewDidLoad()
{
super.viewDidLoad()
let url = "http://vevoplaylist-live.hls.adaptive.level3.net/vevo/ch1/appleman.m3u8" //"http://68.235.37.11:1935/vietmagazine/vietmagazine/playlist.m3u8"
let playerItem = AVPlayerItem( URL:NSURL( string:url )! )
player = AVPlayer(playerItem:playerItem)
avPlayerLayer = AVPlayerLayer(player: player)
// Add the layer to the view
playerView.layer.insertSublayer(avPlayerLayer, atIndex: 0)
}
#IBAction func playVIdeo(sender: AnyObject)
{
avPlayerLayer.player!.play()
}
#IBAction func change(sender: AnyObject)
{
avPlayerLayer.player!.pause()
self.performSegueWithIdentifier("CustomSegue", sender: self)
}
override func viewWillLayoutSubviews()
{
super.viewWillLayoutSubviews()
// Layout subviews manually
avPlayerLayer.frame = playerView.bounds
}
}
CustomSegue.swift:
class CustomSegue: UIStoryboardSegue
{
override func perform()
{
let sourceVC = self.sourceViewController
let destinationVC = self.destinationViewController
sourceVC.view.addSubview(destinationVC.view)
destinationVC.view.transform = CGAffineTransformMakeScale(0.05, 0.05)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
destinationVC.view.transform = CGAffineTransformMakeScale(1.0, 1.0)
} ) { (finished) -> Void in
destinationVC.view.removeFromSuperview()
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in
sourceVC.presentViewController(destinationVC, animated: false, completion: nil)
})
}
}
}
Thanks.
Would passing the AVPlayer instance be enough to pass the player and its state?
Not tested, but does this work? In the first view controller:
class ViewController: UIViewController {
var player = AVPlayer()
#IBAction func myAction(sender: AnyObject) {
performSegueWithIdentifier("TheSegue", sender: self)
}
}
class MySegue: UIStoryboardSegue {
override func perform() {
let viewController = sourceViewController as! ViewController
let secondViewController = destinationViewController as! SecondViewController
secondViewController.player = viewController.player
sourceViewController.presentViewController(destinationViewController, animated: true, completion: nil)
}
}
And in the second view controller:
class SecondViewController: UIViewController {
var player: AVPlayer!
override func viewDidLoad() {
// Do something with the player
}
}
Note, this answer show how to check an AVPlayer;'s state.
my app is a portrait only app, but I have one view controller which is displaying a live stream, using an AVPlayerViewController.
To allow landscape for the full screen view of that player I wrote this method in the AppDelegate.swift:
func application(application: UIApplication, supportedInterfaceOrientationsForWindow window: UIWindow?) -> Int {
var orientation = UIInterfaceOrientationMask.Portrait
if let presentedController = window?.rootViewController?.presentedViewController? {
if presentedController.isKindOfClass( NSClassFromString("AVFullScreenViewController").self ) {
orientation = .AllButUpsideDown
} else if let navController = presentedController as? UINavigationController {
if navController.topViewController.isKindOfClass( NSClassFromString("AVFullScreenViewController").self ) {
orientation = .AllButUpsideDown
}
}
}
return Int(orientation.rawValue)
}
This is how I call initialise my AVPlayer:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showLiveStream" {
SVProgressHUD.show()
var queue: dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(queue, {
let streamURL = NSURL(string: liveStreamURL)
let playerItem = AVPlayerItem(URL: streamURL)
let player = AVPlayer(playerItem: playerItem)
dispatch_async(dispatch_get_main_queue(), {
SVProgressHUD.dismiss()
var playerViewController = segue.destinationViewController as AVPlayerViewController
playerViewController.player = player
})
})
}
}
The problem: when I open up the full screen view of the player, then change to landscape and then click "Done" to dismiss the full screen view, my app stays in landscape. But I want it to rotate to portrait again. How can I do that?
Rather than implementing application(_:supportedInterfaceOrientationsForWindow:) try implementing supportedInterfaceOrientations() on each view controller. So for example:
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
This will ensure that the view controller cannot be displayed in landscape, so when dismissing the video player, it will come straight back to a portrait window.
Update Objective-C:
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
What i did to fix this issue, create a new class with a 'viewDidLayoutSubviews' function and override it!
final class NewMoviePlayerViewController: AVPlayerViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if view.bounds == contentOverlayView?.bounds {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
}
}
Add These Lines in Appdelegate and your Required View Controller
-(BOOL)shouldAutorotate
{
return NO;
}
-(NSUInteger)supportedInterfaceOrientations
{
//LandScapeMode:- UIInterfaceOrientationMaskLandscape;
//PortraitMode:-
return UIInterfaceOrientationMaskPortrait
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
//LandScapeMode:- UIInterfaceOrientationLandscapeRight;
// ProtraitMode:-
return UIInterfaceOrientationPortrait
}