【问题标题】:How do I intercept tapping of the done button in AVPlayerViewController?如何拦截点击 AVPlayerViewController 中的完成按钮?
【发布时间】:2015-04-24 15:30:56
【问题描述】:

我在自定义UIViewController 的viewDidAppear 方法中创建了AVPlayerViewController 和附加的AVPlayer。但是,当我按下“完成”按钮时,我的自定义视图控制器会自动关闭。

我想拦截此操作以使用我自己的展开 Segue,但我不知道如何执行此操作。我找到了MPMoviePlayerViewController 的示例,但没有找到AVPlayerViewController。

我为MPMoviePlayerViewController 找到的代码如下:

- (void)playVideo:(NSString *)aVideoUrl {
    // Initialize the movie player view controller with a video URL string
    MPMoviePlayerViewController *playerVC = [[[MPMoviePlayerViewController alloc] initWithContentURL:[NSURL URLWithString:aVideoUrl]] autorelease];

    // Remove the movie player view controller from the "playback did finish" notification observers
    [[NSNotificationCenter defaultCenter] removeObserver:playerVC
                                                name:MPMoviePlayerPlaybackDidFinishNotification
                                              object:playerVC.moviePlayer];

    // Register this class as an observer instead
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(movieFinishedCallback:)
                                                 name:MPMoviePlayerPlaybackDidFinishNotification
                                               object:playerVC.moviePlayer];

    // Set the modal transition style of your choice
    playerVC.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;

    // Present the movie player view controller
    [self presentModalViewController:playerVC animated:YES];

    // Start playback
    [playerVC.moviePlayer prepareToPlay];
    [playerVC.moviePlayer play];
}

- (void)movieFinishedCallback:(NSNotification *)aNotification {
    // Obtain the reason why the movie playback finished
    NSNumber *finishReason = [[aNotification userInfo] objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];

    // Dismiss the view controller ONLY when the reason is not "playback ended"
    if ([finishReason intValue] != MPMovieFinishReasonPlaybackEnded) {
        MPMoviePlayerController *moviePlayer = [aNotification object];

        // Remove this class from the observers
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:MPMoviePlayerPlaybackDidFinishNotification
                                                      object:moviePlayer];

        // Dismiss the view controller
        [self dismissModalViewControllerAnimated:YES];
    }
}

我就这个问题向 Apple 询问过,他们的回答如下:

感谢您联系 Apple 开发者技术支持 (DTS)。我们的 工程师审查了您的请求并得出结论认为 鉴于没有支持的方式来实现所需的功能 目前正在发布系统配置。

【问题讨论】:

    标签: ios objective-c ios8 xcode6


    【解决方案1】:

    Apple 没有提供处理“完成”按钮的内置方法这一事实令人失望。

    我不想从 AVPlayerViewController 继承,因为它不受 Apple 支持,并且可能会在下一次 iOS 更新中打开一大堆蠕虫。

    我的解决方法是每 200 毫秒触发一次计时器并检查以下情况:

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

    播放器的rate 属性为0 表示视频不再播放。如果视图控制器正在被关闭或已经被关闭,我们可以安全地假设用户点击了“完成”按钮。

    【讨论】:

    • 这是 imo 最安全的解决方案。
    【解决方案2】:

    我对 AVPlayerViewController 进行了子类化,并从 viewWillDisappear 发布了一条通知,以指示 AVPlayerViewController 被解除。

    - (void) viewWillDisappear:(BOOL)animated {
        [[NSNotificationCenter defaultCenter] postNotificationName:kPlayerViewDismissedNotification object:nil];
        [super viewWillDisappear:animated];
    }
    

    这可能不是 100% 正确(因为如果您在 AVPlayerViewController 上显示另一个视图,它会失败),但它对我有用,因为 AVPlayerViewController 始终位于堆栈的顶部。

    【讨论】:

    • 当用户按下完成按钮时,我还想继续在小屏幕上播放视频,所以我需要 avplayer 对象,我该怎么办?
    • AVPlayerViewController 将在您按下完成时停止播放并释放 AVPlayer。根据您的需要,创建您自己的带有 AVPlayer 的 ViewController,当用户按下完成时,只需调整视图的大小。 developer.apple.com/library/ios/samplecode/AVPlayerDemo/…有一个创建自己的AVPlayerViewController的例子
    • 如果我不想继承 AVPlayerViewController(正如 Apple 建议的那样:不要继承 AVPlayerViewController。不支持覆盖此类的方法并导致未定义的行为。这里:developer.apple.com/library/prerelease/ios/documentation/…)是否有获取通知的方式?
    • 如果流有字幕并且你按下字幕按钮 viewwilldisappear 仍然会被调用
    • 根据 Apple Docs:不要继承 AVPlayerViewController。不支持覆盖此类的方法,并导致未定义的行为。 来源:developer.apple.com/library/ios/documentation/AVFoundation/…
    【解决方案3】:

    我通过保持对 AVPlayerViewController 实例的弱引用来解决问题,并使用计时器进行监控,其中引用变为 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()
            }
    }
    

    【讨论】:

      【解决方案4】:

      我不确定这是否有助于您的用例,但如果您可以针对 iOS 12+,AVPlayerViewController 确实提供了一个委托属性/协议 (AVPlayerViewControllerDelegate) 以及一些可能有帮助的相关委托方法(见下文): 特别是在 iOS 12+ 上:

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

      在 tvOS 11+ 上:

          func playerViewControllerShouldDismiss(_ playerViewController: AVPlayerViewController) -> Bool 
          func playerViewControllerWillBeginDismissalTransition(_ playerViewController: AVPlayerViewController) 
          func playerViewControllerDidEndDismissalTransition(_ playerViewController: AVPlayerViewController)
      

      【讨论】:

      • 我尝试了 willEndFullScreenPresentationWithAnimationCoordinator 和 coordinator.notifyWhenInteractionChanges { ... 但这表示 vc(在本例中为 avplayervc)开始在转换中离开全屏模式的交互 - 但是该转换可能以vc回到全屏模式,或者被解雇(或者可能是别的什么)。这在 iOS 上很明显,在我开始拖动关闭 AVPlayerViewController 后调用了 notifyWhenInteractionChanges { ... ,但将其滑回以使 VC 返回全屏。
      【解决方案5】:

      您可以通过继承 AVPLayerViewController 来实现。但是由于这个原因,存在一些未定义的行为。 (在 Apple 文档中给出。见下文) 我也尝试子类化 AVPlayerViewController 并且遇到了内存问题。

      根据 Apple 文档:

      不要继承 AVPlayerViewController。覆盖这个类的 方法不受支持并导致未定义的行为。

      目前,当 AVPlayerViewController 被解除时,它们不提供回调。 请参阅 Apple 开发者论坛:

      在线程 1 苹果人说:

      我仍然相信管理 AVPlayerViewController 的退出 使用上面推荐的手势方法手动实例 将是了解播放器视图控制器的最可靠方法 已被解雇,因为您将在 那一点

      希望对你有帮助!


      嗯,这个问题已经解决了。您可以添加一个按钮作为 AVPlayerViewController 的子视图。通过这样做,您可以拦截完成按钮点击手势。

      【讨论】:

        【解决方案6】:

        不敢相信没有人介绍最明显的解决方案:只需触发通知或在AVPlayerViewController 的-dealloc 方法中执行您需要的任何操作。 只是不要持有 strong 对其的引用,否则 -dealloc 将不会被调用。

        如果您不破坏内部逻辑(例如,不从被覆盖的方法中调用 super),子类化 AVPlayerViewController 没有任何问题(即使文档另有说明)。我多年来一直在使用以下代码(自 iOS 8 推出以来),没有任何运行时或 AppStore 提交问题:

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

        所以,要回答这个问题,只需将其添加到 AVPlayerViewController 子类:

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

        如果你真的害怕继承,那么通过将关联对象附加到AVPlayerViewController 来使用智能技术,你可以控制其-dealloc,请参见此处:https://stackoverflow.com/a/19344475


        评论其他解决方案:

        • 计时器:仅作为最后的手段使用,不要在代码中添加计时器
        • 使用属性观察来了解AVPlayer 的rate 何时变为0.0f 并检查AVPlayerViewController 的isBeingDismissed:聪明,但是当玩家事先暂停时失败(用计时器代替有效,当然)
        • 使用AVPlayerViewController的-viewWillDisappear::当播放器顶部出现某些东西时可能会失败,例如内置字幕选择器。一个类似的解决方案是检查当前视图控制器的-viewWillAppear:,如果一个播放器之前出现并且应该 100% 的时间工作。
        • 在关闭按钮上添加另一个动作:我在iOS 8-13上成功使用了这个解决方案很长时间,但是在我从Xcode 9切换到10后它突然停止工作(即将用于构建的iOS SDK从11提升到12)

        【讨论】:

          【解决方案7】:

          由于这里似乎没有任何完美的答案,因此您可以在某些情况下使用的解决方法是监视 AVPlayer 是否仍在播放并设置观察者以防它在播放完后自动关闭。

            var player:AVPlayer = AVPlayer()
            var videoPlayTimer:NSTimer = NSTimer()
          
            func playVideo(action: UIAlertAction)  -> Void {
          
              player = AVPlayer(URL: NSURL(fileURLWithPath: myFilePath))
              player.actionAtItemEnd = .None
          
              let playerController = AVPlayerViewController()
              playerController.player = player
              self.presentViewController(playerController, animated: true) {      
                self.player.play()
                self.monitorVideoPlayStatus()
                NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(MyViewController.onVideoEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: self.player.currentItem)
              }
            }
          
            //setting a timer to monitor video play status in case it is closed by user
            func monitorVideoPlayStatus(){
              if ((player.rate != 0) && (player.error == nil)) {
                NSLog("player is playing")
                videoPlayTimer = NSTimer.after(0.5, monitorVideoPlayStatus)
              } else {
                NSLog("player is NOT playing")
                onVideoEnd()
              }
            }
          
            //will be called when video plays all the way through
            func onVideoEnd(note: NSNotification){
              NSLog("onVideoEnd")
              onVideoEnd()
          
              //canceling video play monitor
              videoPlayTimer.invalidate()
            }
          
            func onVideoEnd(){
              NSLog("finished playing video")
              NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
          
              //*******
              //DO WHATEVER YOU WANT AFTER VIDEO HAS ENDED RIGHT HERE
              //*******
            }
          

          【讨论】:

          • 如果用户暂停视频怎么办?这算作停留吗?
          【解决方案8】:

          这是我如何检测点击的代码。

          在应用程序委托类中。但它会检测在该视图中找到的每个按钮。您可以添加一些控件,检查标题之类的。

          -(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");
          }
          

          【讨论】:

            【解决方案9】:

            如果你有一个显示AVPlayerViewController 的视图控制器 A,你可能会在 VC A 中检查 viewDidAppear/viewWillAppear。无论何时调用它们,至少我们会知道 AVPlayerViewController 不再显示,因此不应该正在玩。

            【讨论】:

              【解决方案10】:

              一种方法是通过在 AVPlayerViewController 的子视图中搜索相应的 UIButton,向 AVPlayerViewController 上现有的“完成”按钮添加额外的操作。找到按钮后,使用 addTarget:action:forControlEvents 添加自定义操作:

              至少这对我有用。

              - (UIButton*)findButtonOnView:(UIView*)view withText:(NSString*)text
              {
                  __block UIButton *retButton = nil;
              
                  [view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                      if([obj isKindOfClass:[UIButton class]]) {
                          UIButton *button = (UIButton*)obj;
                          if([button.titleLabel.text isEqualToString:text]) {
                              retButton = button;
                              *stop = YES;
                          }
                      }
                      else if([obj isKindOfClass:[UIView class]]) {
                          retButton = [self findButtonOnView:obj withText:text];
              
                          if(retButton) {
                              *stop = YES;
                          }
                      }
                  }];
              
                  return retButton;
              }
              
              - (void)showPlayer:(AVPlayer*)player
              {
                  AVPlayerViewController *vc = [[AVPlayerViewController alloc] init];
                  vc.player = player;
              
                  [self presentViewController:vc animated:NO completion:^{
                      UIButton *doneButton = [self findButtonOnView:vc.view withText:@"Done"];
                      [doneButton addTarget:self action:@selector(doneAction:) forControlEvents:UIControlEventTouchUpInside];
                      [vc.player play];
                  }];
              
              }
              
              - (void)doneAction:(UIButton*)button
              {
                  // perform any action required here
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-11-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-15
                • 1970-01-01
                相关资源
                最近更新 更多