web-dev-qa-db-ja.com

AVPlayerViewControllerで完了ボタンのタップをインターセプトするにはどうすればよいですか?

カスタムAVPlayerViewControllerAVPlayerメソッドでviewDidAppearと添付UIViewControllerを作成しました。しかし、「完了」ボタンを押すと、カスタムビューコントローラーが自動的に閉じます。

自分の巻き戻しセグエを使用するためにこのアクションをインターセプトしたいのですが、これを行う方法がわかりません。 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 Developer Technical Support(DTS)にお問い合わせいただきありがとうございます。当社のエンジニアがお客様のリクエストを確認したところ、現在出荷されているシステム構成では、必要な機能を実現するためのサポートされた方法はないと結論付けました。

18
Simon

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()
        }
}
5
Marco Mardegan

私はAVPlayerViewControllerをサブクラス化し、viewWillDisappearからの通知を投稿して、AVPlayerViewControllerの終了を示しています。

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

これは100%正しくない可能性があります(AVPlayerViewControllerの上に別のビューが表示されていると失敗するため)が、AVPlayerViewControllerは常にスタックの一番上にあるため、うまくいきました。

5
dev

Appleが[完了]ボタンを処理するための組み込みの方法を提供しないという事実は残念です。

Appleではサポートされていないため、AVPlayerViewControllerからの継承を希望していませんでした。おそらく、次のiOSアップデートのいずれかでワームの缶を開くでしょう。

私の回避策は、200ミリ秒ごとにタイマーを起動し、次の状態を確認することです。

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

プレーヤーのrateプロパティが0の場合は、ビデオが再生されていないことを示します。また、View Controllerが閉じられている場合、または既に閉じられている場合は、ユーザーが[完了]ボタンをクリックしたと想定できます。

4
Zyphrax

AVPLayerViewControllerをサブクラス化することでそれを行うことができます。ただし、これにより未定義の動作がいくつかあります。 (Apple docs。以下を参照)で示されます)また、AVPlayerViewControllerをサブクラス化してみましたが、メモリの問題に直面しました。

Apple Docs:によると

AVPlayerViewControllerをサブクラス化しないでください。このクラスのメソッドのオーバーライドはサポートされておらず、未定義の動作を引き起こします。

現在、AVPlayerViewControllerが閉じられた場合、コールバックは提供されません。 Apple開発者フォーラム:

Thread1 Apple=男は言う:

上記の推奨ジェスチャーメソッドを使用してAVPlayerViewControllerインスタンスの出口を手動で管理することは、プレーヤービューコントローラーが却下されたことを確認する最も信頼できる方法であると私はまだ信じています。

それが役に立てば幸い!


まあ、問題の修正があります。ボタンをAVPlayerViewControllerのサブビューとして追加できます。そうすることで、完了したボタンタップジェスチャを傍受できます。

3
Utsav Dusad

誰も最も明白な解決策を導入したとは信じられません:通知を発行するか、AVPlayerViewController-deallocメソッドで必要なことを実行してください。 strongへの参照を保持しないでください。それ以外の場合、-deallocは呼び出されません。

内部ロジックを壊さない場合(たとえば、オーバーライドされたメソッドからAVPlayerViewControllerを呼び出さない場合)は、superのサブクラス化に問題はありません(ドキュメントで特に断りがない場合でも)。私は何年も(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


他のソリューションについてコメントするには:

  • タイマー:最後の手段としてのみ使用し、コードをタイマーで乱雑にしないでください
  • プロパティの監視を使用して、AVPlayerrate0.0fになったことを確認し、AVPlayerViewControllerisBeingDismissedを確認します。ただし、プレーヤーを事前に一時停止すると失敗します(もちろん代わりにタイマーで動作します)
  • AVPlayerViewController-viewWillDisappear:を使用:プレーヤーの上に何かが表示されると失敗する可能性があります。組み込みの字幕セレクタ。同様の解決策は、プレーヤーが以前に提示され、100%の時間動作する必要がある場合、提示するビューコントローラーの-viewWillAppear:をチェックインすることです。
  • [閉じる]ボタンに別のアクションを追加:このソリューションをiOS 8-13で長時間正常に使用しましたが、Xcode 9から10に切り替えた後、突然機能しなくなりました(つまり、ビルドに使用するiOS SDKを11から12に上げました)
1
kambala

これがあなたのユースケースに役立つかどうかはわかりませんが、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)
0
mamadlin

クリックを検出するためのコードを以下に示します。

アプリケーションデリゲートクラス内。ただし、そのビューで見つかったすべてのボタンを検出します。あなたはいくつかのコントロールを追加することができ、そのようなタイトルを確認してください。

-(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
mialkan

ここには完全な答えはないようですので、状況によっては、AVPlayerがまだ再生中かどうかを監視し、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
    //*******
  }
0

AVPlayerViewControllerを表示するビューコントローラーAがある場合は、おそらくVC Aの内部でviewDidAppear/viewWillAppearをチェックインできます。これらが呼び出されるときはいつでも、少なくともAVPlayerViewControllerは表示されなくなったため、再生されません。

0
Jonny