¿Cómo interceptar el toque del button hecho en AVPlayerViewController?

AVPlayerViewController un AVPlayerViewController y un AVPlayer adjunto en el método viewDidAppear de un UIViewController personalizado. Sin embargo, cuando presiono el button "Listo" mi controller de vista personalizado se descarta automáticamente.

Me gustaría interceptar esta acción para usar Segue, mi propio desenrollo, pero no estoy seguro de cómo hacerlo. He encontrado ejemplos para MPMoviePlayerViewController pero no para AVPlayerViewController .

El código que encontré para MPMoviePlayerViewController encuentra a continuación:

 - (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]; } } 

Le pregunté a Apple sobre este problema y me respondieron de la siguiente manera:

Gracias por comunicarse con el Soporte técnico para desarrolladores de Apple (DTS). Nuestros ingenieros han revisado su request y han llegado a la conclusión de que no existe una forma compatible para lograr la funcionalidad deseada dadas las configuraciones del sistema de envío actual.

Subclass AVPlayerViewController y envío una notificación de viewWillDisappear para indicar que se desestima AVPlayerViewController.

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

Esto podría no ser 100% correcto (ya que fallaría si tiene otra vista en AVPlayerViewController), pero funcionó para mí ya que AVPlayerViewController siempre está en la parte superior de la stack.

Puede hacerlo subclarando AVPLayerViewController. Pero hay un comportamiento indefinido debido a esto. (Dado en los documentos de Apple. Véase a continuación) También traté de subclass AVPlayerViewController y enfrenté el problema de memory.

De acuerdo con los Apple Docs:

No subclasss AVPlayerViewController. La anulación de los methods de esta class no es compatible y da como resultado un comportamiento no definido.

Actualmente, no ofrecen una callback cuando se descarta AVPlayerViewController. Ver foro de desarrolladores de Apple:

  • Thread1
  • Thread2

En thread1 dice:

Sigo creyendo que administrar la salida de la instancia de AVPlayerViewController manualmente utilizando el método de gestos recomendado arriba sería la manera más confiable de saber que el controller de vista del jugador ha sido descartado, ya que estará a cargo de su rechazo en ese momento

¡Espero eso ayude!


Bueno, hay una solución al problema. Puede agregar un button como una subvista de AVPlayerViewController. Al hacerlo, podría interceptar el gesto de hacer clic en el button.

El hecho de que Apple no ofrezca una forma integrada de manejar el button Hecho es decepcionante.

No tenía ganas de henetworkingar AVPlayerViewController, ya que no es compatible con Apple y probablemente abrirá una lata de gusanos en una de las próximas actualizaciones de iOS.

Mi solución consiste en disparar un timer cada 200 mseg y verificar la siguiente condición:

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

La propiedad de rate del reproductor de 0 indica que el video ya no se reproduce. Y si el controller de vista está siendo rechazado o ya rechazado, podemos asumir con security que el usuario hizo clic en el button Listo.

Dado que no parece haber ninguna respuesta perfecta aquí, una solución que puede usar para algunas situaciones es controlar si AVPlayer todavía está reproduciendo y configurar un observador en caso de que se cierre automáticamente después de haber sido reproducido completamente.

  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 //******* } 

Aquí está el código de cómo logré detectar los clics.

En la class de delegado de la aplicación. Pero detecta que todos los botones se encuentran en esa vista. Puede agregar un poco de control, verificar el título algo así.

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

Si tiene un controller de vista A que presenta un AVPlayerViewController , probablemente pueda comprobar en la vistaDidAppear / viewWillAppear dentro del VC A. Siempre que se llamen, al less sabremos que el AVPlayerViewController ya no se muestra y, por lo tanto, no debería reproducirse.

Una forma es agregar una acción adicional al button "Listo" existente en AVPlayerViewController buscando el button UIB respectivo dentro de las subpistas de AVPlayerViewController. Una vez que se encuentra el button, agregue una acción personalizada con addTarget: action: forControlEvents:

Al less esto funciona para mí.

 - (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 requinetworking here }