andreipitis / ASPVideoPlayer

A simple video player that allow animations to be performed on the view during playback.
MIT License
89 stars 43 forks source link

Full Screen Mode #12

Closed rehannali closed 7 years ago

rehannali commented 7 years ago

There is no method related to Full-screen mode. There should be button simple at the bottom right corner so you should choose whether the fullscreen mode is portrait or landscape.

Please add this functionality or it you already added it and I don't know then sorry for my mistake and Could you please tell me whether this functionality is implemented or not?

andreipitis commented 7 years ago

Hi @rehannali,

Right now the video player changes from portrait to landscape when the ViewController changes orientation.

If you want to force it to be in Landscape in a Portrait only app, you can set up constraints for all 4 edges and use the following code in viewDidLoad():

//videoPlayer is an instance of ASPVideoPlayer positioned within the view of a ViewController
//view is the view of the ViewController
videoPlayer.transform = CGAffineTransform(rotationAngle: .pi / 2.0)

let padding = (view.bounds.height - view.bounds.width) / 2.0

rightConstraint.constant = -padding
leftConstraint.constant = -padding
topConstraint.constant = padding
bottomConstraint.constant = padding
view.layoutIfNeeded()

Orientation and layout changes should ideally be handled by your app, this way you can create whatever layout configuration and animation transition you like.

I will probably create a button for this in the UI and provide a closure that you can use to specify your own rotation logic.

rehannali commented 7 years ago

Hi @andreipitis Actually, I want like Youtube manual rotation i.e. When I pressed a button in little screen it forced player to rotate the view in full-screen mode automatically. That's why I need this to perform custom player. If this is possible now, can you please tell me how i can do it?

andreipitis commented 7 years ago

There is no builtin functionality for this, but as I said in my previous comment, you can implement that functionality yourself. There is no button to tap, but you could either implement your own UI (just copy the code for ASPVideoPlayerControls and add that button, or subclass ASPBasicControls and implement your custom UI with buttons, slider, etc.), or just use some kind of gesture to start the animation.

In the future I will add a button to the existing UI, but the rotation functionality will not be performed automatically. As I said before, the users of the component should implement their own rotation/fullscreen functionality because I can't know what kind of layout everyone has and this way everyone can create the exact layout and animation that fits their needs.

moheny commented 7 years ago

Thank you for creating this wonderful library. can you please provide the full code how to make the videoPlayer to streched all over the screen in horizontal (even tho the app is portrait only) . the following line does work: videoPlayer.transform = CGAffineTransform(rotationAngle: .pi / 2.0) but how can I make it stretch all over the screen ? the constraint part is unclear. BTW my play is 100width and 100height, and when i click on some button i want to make it stretch all over the screen, only the stretching is my problem... thanks in advance.

andreipitis commented 7 years ago

Hi @moheny,

Unfortunately without knowing your exact layout I cannot provide the specific solution for your use case. I am assuming that you have a similar layout to this one:

screen shot 2017-06-16 at 22 20 14

In this case, the containerView has a fixed height, and has a single child that fills the entire view (the videoPlayer).

With the above layout the folowing code works:

    var isExpanded: Bool = false
    var previousConstraints: [NSLayoutConstraint] = []

    func rotate() {
        let views: [String:Any] = ["videoPlayer": videoPlayer]

        if isExpanded == false {
            UIView.animate(withDuration: 0.25, delay: 0.0, options: [], animations: {
                self.containerView.removeConstraints(self.videoPlayer.constraints)
                self.view.addSubview(self.videoPlayer)

                let padding = (self.view.bounds.height - self.view.bounds.width) / 2.0

                let metrics: [String:Any] = ["padding":padding, "negativePadding":-padding]

                self.videoPlayer.transform = CGAffineTransform(rotationAngle: .pi / 2.0)

                var constraints: [NSLayoutConstraint] = []
                constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|-(negativePadding)-[videoPlayer]-(negativePadding)-|", options: [], metrics: metrics, views: views))

                constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|-(padding)-[videoPlayer]-(padding)-|", options: [], metrics: metrics, views: views))

                self.view.addConstraints(constraints)
                self.view.layoutIfNeeded()

                self.previousConstraints = constraints
            }, completion: { finished in
                self.isExpanded = true
            })
        } else {
            UIView.animate(withDuration: 0.25, delay: 0.0, options: [], animations: {
                self.view.removeConstraints(self.previousConstraints)
                self.containerView.addSubview(self.videoPlayer)

                self.videoPlayer.transform = CGAffineTransform(rotationAngle: 0.0)

                var constraints: [NSLayoutConstraint] = []

                constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:|[videoPlayer]|", options: [], metrics: nil, views: views))

                constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:|[videoPlayer]|", options: [], metrics: nil, views: views))

                self.containerView.addConstraints(constraints)
                self.view.layoutIfNeeded()

                self.previousConstraints = constraints
            }, completion: { finished in
                self.isExpanded = false
            })
        }
    }

Obviously it's not the cleanest code, and there may be some better solutions to achieve this functionality depending on the layout you use. This is just a quick test that I came up with.

Basically the rotation only affects the visual representation of the player, the constraints are kept as if the player were not rotated at all. In order to get the desired result, you have to change the constraints so that the player gets centred and stretched in such a way that the height is equal to the width of the screen and the width of the player is equal to the height of the screen.

This is how the animation would look without the rotation code:

aspvideoplayerrotation

moheny commented 7 years ago

Thank you of much for taking your time and answer. The layout you have created is the exact layout i have, and i guess my problem is I didn't remove the constraint before I have tried to change it, i'll try to play with it and update you . BTW, I think it's easier using an library for the layout instead of writing it :-) something like snapKit it 5 times easier ..

kyazdani commented 6 years ago

Hey @andreipitis this post helped me a lot. Only issue I'm facing is on the iPhone X with the safe area. for some reason I cannot see why the controls I have as part of the videoPlayer do not fit within the safe area. Do you have any suggestions for this? Many thanks.

andreipitis commented 6 years ago

Hi @kyazdani ,

So you have something like this: screen shot 2018-04-19 at 09 15 58

And you want something like this: screen shot 2018-04-19 at 09 15 19

I you want to achieve this, you just need to edit the code in the comment above for the rotate() function.

Replace this:

let metrics: [String:Any] = ["padding":padding, "negativePadding":-padding]

with this:

var bottomPadding: CGFloat = 0

if #available(iOS 11.0, *) {
    if self.view.safeAreaInsets != .zero {
        bottomPadding = self.view.safeAreaInsets.bottom
    }
}

let metrics: [String:Any] = ["padding":padding, "negativePadding":-(padding - bottomPadding)]

I may have misunderstood you, if this was not your issue could you please clarify what you want to achieve?

kyazdani commented 6 years ago

Hi @andreipitis, apologies I didn't see you reply. This works really well. However outside the safe area on your second screenshot you have a white section. I get this same section with this added code. Is there anything you would suggest for filling this bit in black without the rotation looking odd?

Also I'm getting some constraint errors when rotating. I think this is purely down to the setup of my nib. What constraints do you have applied to theContainer View?

Many thanks :-)

andreipitis commented 6 years ago

@kyazdani the easiest way would be to just set the background color of the superview to black.

If that is not an option, you could add another subview with a black background in the containerView so that it is a sibling of the videoPlayer with the same constraints. Then you would simply add the same animations and constraints to that view with a slight modification: instead of setting the negative padding to be equal to -(padding - bottomPadding) you would add a new metric for negative padding with the value -padding so that it fills the entire screen.

The code for this method looks like this:

    func rotate(isExpanded: Bool) {
        let views: [String:Any] = ["videoPlayer": videoPlayer,
                                   "backgroundView": videoPlayerBackgroundView]

        var constraints: [NSLayoutConstraint] = []

        if isExpanded == false {
            self.containerView.removeConstraints(self.videoPlayer.constraints)

            self.view.addSubview(self.videoPlayerBackgroundView)
            self.view.addSubview(self.videoPlayer)
            self.videoPlayer.frame = self.containerView.frame
            self.videoPlayerBackgroundView.frame = self.containerView.frame

            let padding = (self.view.bounds.height - self.view.bounds.width) / 2.0

            var bottomPadding: CGFloat = 0

            if #available(iOS 11.0, *) {
                if self.view.safeAreaInsets != .zero {
                    bottomPadding = self.view.safeAreaInsets.bottom
                }
            }

            let metrics: [String:Any] = ["padding":padding,
                                         "negativePaddingAdjusted":-(padding - bottomPadding),
                                         "negativePadding":-padding]

            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "H:|-(negativePaddingAdjusted)-[videoPlayer]-(negativePaddingAdjusted)-|",
                                               options: [],
                                               metrics: metrics,
                                               views: views))
            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "V:|-(padding)-[videoPlayer]-(padding)-|",
                                               options: [],
                                               metrics: metrics,
                                               views: views))

            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "H:|-(negativePadding)-[backgroundView]-(negativePadding)-|",
                                               options: [],
                                               metrics: metrics,
                                               views: views))
            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "V:|-(padding)-[backgroundView]-(padding)-|",
                                               options: [],
                                               metrics: metrics,
                                               views: views))

            self.view.addConstraints(constraints)
        } else {
            self.view.removeConstraints(self.previousConstraints)

            let targetVideoPlayerFrame = self.view.convert(self.videoPlayer.frame, to: self.containerView)
            let targetVideoPlayerBackgroundViewFrame = self.view.convert(self.videoPlayerBackgroundView.frame, to: self.containerView)

            self.containerView.addSubview(self.videoPlayerBackgroundView)
            self.containerView.addSubview(self.videoPlayer)

            self.videoPlayer.frame = targetVideoPlayerFrame
            self.videoPlayerBackgroundView.frame = targetVideoPlayerBackgroundViewFrame

            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "H:|[videoPlayer]|",
                                               options: [],
                                               metrics: nil,
                                               views: views))
            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "V:|[videoPlayer]|",
                                               options: [],
                                               metrics: nil,
                                               views: views))

            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "H:|[backgroundView]|",
                                               options: [],
                                               metrics: nil,
                                               views: views))
            constraints.append(contentsOf:
                NSLayoutConstraint.constraints(withVisualFormat: "V:|[backgroundView]|",
                                               options: [],
                                               metrics: nil,
                                               views: views))

            self.containerView.addConstraints(constraints)
        }

        self.previousConstraints = constraints

        UIView.animate(withDuration: 2.25, delay: 0.0, options: [], animations: {
            self.videoPlayer.transform = isExpanded == true ? .identity : CGAffineTransform(rotationAngle: .pi / 2.0)
            self.videoPlayerBackgroundView.transform = isExpanded == true ? .identity : CGAffineTransform(rotationAngle: .pi / 2.0)

            self.view.layoutIfNeeded()
        })
    }

There are probably better/cleaner ways to do this, but this is the first thing that came to mind.

For the constraints you can check the Main.storyboard file of the example project, but it's all very simple: top, leading and trailing of the containerView to the superview are 0 and it also has a fixed height of 300 in my example.

The above code should work even if you position the containerView with an offset from the top.

kyazdani commented 6 years ago

Hi that's great. However the other problem presents itself with this method. How would you be able to make the video fit the full size of the phone? For example YouTube when you double tap the screen would increase the size of the video to full size but the controls would stay where they are.

andreipitis commented 6 years ago

With the existing implementation, you can't. You can however create your own controls, by subclassing ASPBasicControls and setting the videoPlayerControls property of ASPVideoPlayer to use your custom class.

You could pretty much copy the implementation of ASPVideoPlayerControls and adjust the layout code for iPhone X according to your needs.