J'ai créé AVPlayerViewController
et une AVPlayer
attachée dans la méthode viewDidAppear
d'une coutume UIViewController
. Cependant, lorsque j'appuie sur le bouton "Terminé", mon contrôleur de vue personnalisé est automatiquement ignoré.
Je voudrais intercepter cette action afin d’utiliser ma propre séquence de déroulement, mais je ne sais pas comment faire. J'ai trouvé des exemples pour MPMoviePlayerViewController
mais pas AVPlayerViewController
.
Le code que j'ai trouvé pour MPMoviePlayerViewController
est ci-dessous:
- (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];
}
}
J'ai demandé à Apple ce problème et ils ont répondu comme suit:
Merci de contacter le support technique pour développeurs Apple (DTS). Notre les ingénieurs ont examiné votre demande et ont conclu qu'il y avait aucun moyen pris en charge pour obtenir la fonctionnalité désirée étant donné le actuellement les configurations du système d'expédition.
Le fait qu'Apple ne fournisse aucun moyen intégré de gérer le bouton Terminé est décevant.
Je n'avais pas envie d'hériter d'AVPlayerViewController, car il n'est pas pris en charge par Apple et ouvrira probablement une boîte de Pandore dans l'une des prochaines mises à jour iOS.
Ma solution consiste à déclencher une minuterie toutes les 200 ms et à vérifier la condition suivante:
if (playerVC.player.rate == 0 &&
(playerVC.isBeingDismissed || playerVC.nextResponder == nil)) {
// Handle user Done button click and invalidate timer
}
La propriété rate
du lecteur, égale à 0, indique que la vidéo n'est plus en cours de lecture. Et si le contrôleur de vue est licencié ou déjà licencié, nous pouvons supposer en toute sécurité que l'utilisateur a cliqué sur le bouton Terminé.
Je sous-classe AVPlayerViewController et je publie une notification de viewWillDisappear pour indiquer le rejet de AVPlayerViewController.
- (void) viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] postNotificationName:kPlayerViewDismissedNotification object:nil];
[super viewWillDisappear:animated];
}
Cela pourrait ne pas être correct à 100% (car cela échouerait si une autre vue était affichée sur AVPlayerViewController), mais cela a fonctionné pour moi car AVPlayerViewController est toujours au sommet de la pile.
J'ai résolu le problème en conservant une référence faible à l'instance d'AVPlayerViewController et en surveillant avec un minuteur où la référence passait à zéro.
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()
}
}
Vous pouvez le faire en sous-classant AVPLayerViewController. Mais il y a un comportement indéfini pour cette raison. (Donné dans la documentation Apple. Voir ci-dessous) J'ai également essayé de sous-classer AVPlayerViewController et j'ai rencontré un problème de mémoire.
Selon les documents Apple:
Ne sous-classez pas AVPlayerViewController. Ignorer cette classe methods n'est pas pris en charge et entraîne un comportement non défini.
Actuellement, ils n'offrent pas de rappel lorsque AVPlayerViewController est licencié ..__ Voir Forum des développeurs Apple:
Dans thread1, le gars d'Apple dit:
Je crois toujours que la gestion de la sortie de AVPlayerViewController instance manuellement en utilisant la méthode gestuelle recommandée ci-dessus serait le moyen le plus fiable de savoir que le contrôleur de vue du lecteur a été congédié, puisque vous serez responsable de son licenciement à ce point
J'espère que ça aide!
Eh bien, il y a une solution au problème. Vous pouvez ajouter un bouton en tant que sous-vue de AVPlayerViewController. Ce faisant, vous pourriez intercepter le geste de frappe effectué avec le bouton.
Voici comment j'ai réussi à détecter les clics.
En classe de délégué d'application. Mais il détecte tous les boutons trouvés dans cette vue. Vous pouvez ajouter un contrôle, vérifier le titre quelque chose comme ça.
-(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");
}
Comme il ne semble pas y avoir de réponses parfaites ici, une solution de contournement que vous pouvez utiliser dans certaines situations consiste à contrôler si le lecteur AVPlayer est toujours en cours de lecture et à définir un observateur au cas où il se fermerait automatiquement après une lecture en profondeur.
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
//*******
}
Si vous avez un contrôleur de vue A qui présente une AVPlayerViewController
, vous pouvez probablement vérifier dans viewDidAppear/viewWillAppear dans VC A. Chaque fois que ces appels sont appelés, nous saurons au moins que AVPlayerViewController n'est en jouant.