How do I intercept tapping of the done button in AVPlayerViewController?

前端 未结 10 684
渐次进展
渐次进展 2021-01-03 22:45

I created an AVPlayerViewController and an attached AVPlayer in the viewDidAppear method of a custom UIViewController. Ho

相关标签:
10条回答
  • 2021-01-03 23:06

    The fact that Apple provides no built-in way to handle the Done button is disappointing.

    I didn't feel like inheriting from AVPlayerViewController, because it isn't supported by Apple and will probably open up a can of worms in one of the next iOS updates.

    My workaround is to have a timer fire every 200 msec and check for the following condition:

    if (playerVC.player.rate == 0 &&
       (playerVC.isBeingDismissed || playerVC.nextResponder == nil)) {
      // Handle user Done button click and invalidate timer
    }
    

    The player's rate property of 0 indicates that the video is no longer playing. And if the view controller is being dismissed or already dismissed, we can safely assume that the user clicked the Done button.

    0 讨论(0)
  • 2021-01-03 23:06

    I solved by keeping a weak reference to the AVPlayerViewController instance, and monitoring with a timer where the reference change to nil.

    private weak var _playerViewController : AVPlayerViewController? // global reference
        ...
        ...
        let playerController = AVPlayerViewController() // local reference
        ...
        self.present(playerController, animated: true) { [weak self] in
            playerController.player?.play()
            self?._playerViewController = playerController
            // schedule a timer to intercept player dismissal
            Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] (timer) in
            if self?.playerViewController == nil {
                // player vc dismissed
                timer.invalidate()
            }
    }
    
    0 讨论(0)
  • 2021-01-03 23:06

    Can't believe nobody introduced the most obvious solution: just fire a notification or do whatever you need in AVPlayerViewController's -dealloc method. Just don't hold a strong reference to it, otherwise -dealloc won't be called.

    There's nothing wrong with subclassing AVPlayerViewController (even if docs say otherwise), if you don't break internal logic (e.g. not calling super from overridden methods). I've been using the following code for years (since iOS 8 came out) without any runtime or AppStore submission issues:

    @interface VideoPlayerViewController : AVPlayerViewController
    @end
    
    @implementation VideoPlayerViewController
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscape; }
    @end
    
    // now use VideoPlayerViewController instead of AVPlayerViewController
    

    So, to answer the question, just add this to AVPlayerViewController subclass:

    - (void)dealloc {
      // player was dismissed and is going to die, do any cleanup now
    }
    

    If you are really afraid to subclass, then use smart technique by attaching an associated object to AVPlayerViewController, whose -dealloc you can control, see here: https://stackoverflow.com/a/19344475


    To comment on other solutions:

    • timers: use as last resort only, don't clutter your code with timers
    • use property observation to know when AVPlayer's rate becomes 0.0f and check AVPlayerViewController's isBeingDismissed: clever, but fails when player is paused beforehand (doing it with timer instead works, of course)
    • use AVPlayerViewController's -viewWillDisappear:: might fail when something is presented on top of the player, e.g. built-in subtitle selector. A similar solution would be checking in -viewWillAppear: of the presenting view controller if a player was presented before and should work 100% of the time.
    • adding another action to the Close button: I used this solution for a long time successfully on iOS 8-13, but it suddenly stopped working after I switched from Xcode 9 to 10 (i.e. raised iOS SDK used for building from 11 to 12)
    0 讨论(0)
  • 2021-01-03 23:06

    If you have a view controller A that presents a AVPlayerViewController, you could probably check in viewDidAppear/viewWillAppear inside VC A. Whenever those are called, at least we would know that the AVPlayerViewController is no longer shown and thus should not be playing.

    0 讨论(0)
  • 2021-01-03 23:07

    Here is code how I managed to detect clicks.

    In application delegate class. But it detects every buttons find in that view. You can add some control, check title something like that.

    -(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
        if ([window.rootViewController isKindOfClass:[AVPlayerViewController class]]) {
            return UIInterfaceOrientationMaskAll;
        }
        else if(window.rootViewController.presentedViewController != nil)
        {
            if ([window.rootViewController.presentedViewController isKindOfClass:[AVPlayerViewController class]] || [window.rootViewController.presentedViewController isKindOfClass:NSClassFromString(@"AVFullScreenViewController")]) {
                if ([window.rootViewController.presentedViewController isKindOfClass:NSClassFromString(@"AVFullScreenViewController")]) {
                    [self findAllButton:window.rootViewController.presentedViewController.view];
                }
                return UIInterfaceOrientationMaskAll;
            }
        }
        [[UIDevice currentDevice] setValue:@(UIInterfaceOrientationPortrait) forKey:@"orientation"];
        [[UIApplication sharedApplication] setStatusBarHidden:NO];
        return UIInterfaceOrientationMaskPortrait;
    }
    
    -(void)findAllButton:(UIView*)view{
        for (UIView *subV in view.subviews) {
            if ([subV isKindOfClass:[UIButton class]]) {
                NSLog(@"%@",[((UIButton*)subV) titleForState:UIControlStateNormal]);
                [((UIButton*)subV) addTarget:self action:@selector(doneButtonCliked) forControlEvents:UIControlEventTouchUpInside];
            }
            else{
                [self findAllButton:subV];
            }
        }
    }
    -(IBAction)doneButtonCliked{
        NSLog(@"DONECLICK");
    }
    
    0 讨论(0)
  • 2021-01-03 23:10

    I am not sure if this helps your usecase, but if you can target iOS 12+, AVPlayerViewController does provide a delegate property/protocol (AVPlayerViewControllerDelegate) with a few related delegate methods that might help (see below): Particularly on iOS 12+:

        func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) 
    

    On tvOS 11+:

        func playerViewControllerShouldDismiss(_ playerViewController: AVPlayerViewController) -> Bool 
        func playerViewControllerWillBeginDismissalTransition(_ playerViewController: AVPlayerViewController) 
        func playerViewControllerDidEndDismissalTransition(_ playerViewController: AVPlayerViewController)
    
    0 讨论(0)
提交回复
热议问题