18

I want to play local video in AVPlayerViewController but did not find click event of Done button.

My video is able to play in AVPlayerViewController but I did not find next button , previous button and done button click event.

Yogendra Patel
  • 813
  • 2
  • 8
  • 24

9 Answers9

21


I've done this to get Done button click event from AVPlayerViewController.

First of all, Create an extension of Notification.Name like bellow

extension Notification.Name {
static let kAVPlayerViewControllerDismissingNotification = Notification.Name.init("dismissing")
}

Now, Create an extension of AVPlayerViewController and override viewWillDisappear like bellow

// create an extension of AVPlayerViewController
extension AVPlayerViewController {
    // override 'viewWillDisappear'
    open override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // now, check that this ViewController is dismissing
        if self.isBeingDismissed == false {
            return
        }

        // and then , post a simple notification and observe & handle it, where & when you need to.....
        NotificationCenter.default.post(name: .kAVPlayerViewControllerDismissingNotification, object: nil)
    }
}

THIS IS FOR ONLY BASIC FUNCTIONALITY THAT HANDLES DONE BUTTON'S EVENT.

happy coding...

Vatsal Shukla
  • 1,274
  • 12
  • 25
  • How to handle when pause/forward/backward buttons are tapped? – pkc456 Oct 03 '17 at 11:44
  • @pkc456, in my opinion, you should create custom Player using AVPlayerLayer in UIViewController so you can easily handle those events. – Vatsal Shukla Oct 03 '17 at 12:10
  • Much better and safer. – Sam Bing Apr 02 '18 at 11:28
  • 1
    Also a note to anyone who may use this, depending on the implementation you may be adding observers over and over, so don't forget to add them only once and remove them when appropriate. – Sam Bing Apr 02 '18 at 11:37
  • 1
    Thank you for this suggestion. Its been few more hours since I was looking for solution. – Arsalan Jul 03 '18 at 11:24
  • 1
    very handy, concise and notification is really a good call for this case – infinity_coding7 Sep 26 '18 at 20:04
  • Why not `if isBeingDismissed { //post-notification }` instead? I think it's more concise and better represents what you intend the code to accomplish. – Taylor M Dec 15 '20 at 08:36
  • Hi @TaylorM, thanks for suggestion... Fortunately, I'm currently working on VideoEditor so, I'll give it a try and share updates. – Vatsal Shukla Dec 16 '20 at 05:05
  • 1
    The only problem with this solution is that you can start swiping to dismiss the player but then not dismiss it and the notification will be fired anyways. I would suggest posting the notification in `viewDidDisappear` instead. – Gustavo Conde Jul 21 '21 at 06:40
18

Officially there is no Done Button click event, a engineer from Apple said about it here.

As for my research, I found out that there is one but really indirect way to get an event if Done Button was clicked.

I found out that the only variable of AVPlayerViewController, which is changing when done button is clicked is AVPlayerViewController.view.frame. First of all view.frame is appearing in the center of the viewController.

If you present it with animated : true it goes to the bottom of viewController and the back to the center. When done is clicked it goes back to the bottom.

If you present it with animated : false there will be only 2 changes: frame will be at the center of viewController when you start to play video, and at the bottom, when Done is clicked.

So if you add observer to the AVPlayerViewController.view.frame in the callback to present(PlayerViewController, animated : true) you'll get only one call of the observer, right when done button is clicked and video view will be out of the screen.

In my case AVPlayerViewController was presented modally with animation. Code below worked for me:

Swift 3.0

override func viewDidLoad()
{
    super.viewDidLoad()

    let videoURL = NSURL(fileURLWithPath:Bundle.main.path(forResource: "MyVideo", ofType:"mp4")!)
    let player = AVPlayer(url: videoURL as URL)

    present(playerViewController, animated: false) { () -> Void in
        player.play()

        self.playerViewController.player = player
        self.playerViewController.addObserver(self, forKeyPath: #keyPath(UIViewController.view.frame), options: [.old, .new], context: nil)
    }}
    override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?)
{

    print(self.playerViewController.view.frame)
    //Player view is out of the screen and
    //You can do your custom actions
}

Also, I found out when you click Done, AVPlayerViewController is not dismissed and you can see it in ParentViewController.presentedViewController, so you can't add observer to this property

vmchar
  • 1,314
  • 11
  • 15
5

Objective c version: (Based on vmchar answer)

- (void)viewDidLoad
{
    [super viewDidLoad];
        NSURL *url = [NSURL fileURLWithPath:path];
        AVPlayer *player = [AVPlayer playerWithURL:url];
        AVPlayerViewController *AVPlayerVc = [[AVPlayerViewController alloc] init];
        if (AVPlayerVc) {
            [self presentViewController:AVPlayerVc animated:YES completion:^{
                [player play];
                AVPlayerVc.player = player;
                [AVPlayerVc addObserver:self forKeyPath:@"view.frame" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:nil];
            }];

        }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"view.frame"]) {
        CGRect newValue = [change[NSKeyValueChangeNewKey]CGRectValue];
        CGFloat y = newValue.origin.y;
        if (y != 0) {
              NSLog(@"Video Closed");
        }
     }
}
Zouhair Sassi
  • 1,403
  • 1
  • 13
  • 30
  • 1
    Sorry, I didn't mean to reject your edit on https://stackoverflow.com/review/suggested-edits/24465963: I just happened to be editing the post line by line at the same time. – Cœur Nov 01 '19 at 14:40
4

This is a small improvement from the answer by @vmchar:

We can use the .isBeingDismissed method to ensure that the AVPlayerViewController is being closed instead of analysing the frame.

...

let videoURL = NSURL(fileURLWithPath:"video.mp4")
let player = AVPlayer(url: videoURL as URL)

present(playerViewController!, animated: false) { () -> Void in
    player.play()

    self.playerViewController!.player = player
    self.playerViewController!.addObserver(self, forKeyPath:#keyPath(UIViewController.view.frame), options: [.old, .new], context: nil)
...

Then to observe the value

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?)
{

    if (playerViewController!.isBeingDismissed) {  
     // Video was dismissed -> apply logic here
    } 
} 
4

The easiest solution for me was to subclass AVPlayerViewController and add simple completion block to it.

class MYVideoController: AVPlayerViewController {

  typealias DissmissBlock = () -> Void
  var onDismiss: DissmissBlock?

  override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
      onDismiss?()
    }
  }
}

Usage

...
let avPlayerViewController = MYVideoController()
avPlayerViewController.onDismiss = { [weak self] in 
    print("dismiss")
}
inokey
  • 5,434
  • 4
  • 21
  • 33
2

I found a workaround without having to subclass the AVPlayerViewController, which the documentation clearly states you should not (https://developer.apple.com/documentation/avkit/avplayerviewcontroller). It is not pretty, but it got the job done for me by giving me somewhere to run code when the AVPlayerViewController is dismissed.

AnyViewController: UIViewController, AVPlayerViewControllerDelegate {
    private let playerVC = AVPlayerViewController()

    override func viewDidLoad() {
        playerVC.delegate = self        
    }

    func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        if playerViewController.isBeingDismissed {
            playerViewController.dismiss(animated: false) { [weak self] in
                self?.someDismissFunc()
            }
        }
    }
}

It is really messed up that there is no clean way of doing this as far as I can tell :(

Kent Robin
  • 2,066
  • 20
  • 19
  • Not sure if there are any downsides yet, but this seems to work and is the simplest solution so far. Hope others who are looking for a basic implementation for this use case sees it. Thank you for sharing. – Onur Çevik May 08 '20 at 17:15
  • The problem with this solution is that you can start dismissing the player by swiping but then swipe it back into place. This delegate is called anyways and isBeingDismissed is true so you end up thinking the player was dismissed when it wasn't. – Gustavo Conde Jul 21 '21 at 06:42
0

I had the same problem, and I found another trick (works with YoutubePlayer swift framework which plays videos in a webview, but you might be able to adapt it to other uses).

In my case, pressing the Done button and pressing the Pause button on the fullscreen video player both results in a pause event on the YoutubePlayer. However, as the video plays in a different window, I subclassed my application's main window and overrode the becomeKey and resignKey functions to store whether my window is the key window or not, like that:

class MyWindow:UIWindow {
    override func becomeKey() {
        Services.appData.myWindowIsKey = true
    }

    override func resignKey() {
        Services.appData.myWindowIsKey = false
    }
}

Once I have that, I can check whether my window is key when the video state goes to pause - when the Done button was pressed, my window is the key window and I can dismiss my video view controller, and in the Pause case my window is not the key window, so I do nothing.

TheEye
  • 9,280
  • 2
  • 42
  • 58
0

I guess there are lots of ways to skin a cat. I needed to handle the 'Done' click event. In my case, I wanted to make sure I was able to hook into the event after the "X" was clicked and the AVPlayerViewController was COMPLETELY closed (dismissed).

Swift 4.0

protocol TableViewCellVideoPlayerViewControllerDelegate: class {
    func viewControllerDismissed()
}

class MyPlayerViewController: AVPlayerViewController {

    weak var navDelegate: TableViewCellVideoPlayerViewControllerDelegate?

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if self.isBeingDismissed {
            self.navDelegate?.viewControllerDismissed()
        }
    }
}

class TodayCell: UITableViewCell, SFSafariViewControllerDelegate,  TableViewCellVideoPlayerViewControllerDelegate {
    func viewControllerDismissed() {
        self.continueJourney()
    }
 }
Clay Martin
  • 199
  • 1
  • 4
-6

You can look into this Stackoverflow post and here is a github's project for reference. You will have to use this :

 self.showsPlaybackControls = true

Please also have a look into Apple's documentation

Community
  • 1
  • 1
Munahil
  • 2,381
  • 1
  • 14
  • 24