カスタムAVPlayerViewController
のAVPlayer
メソッドで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)にお問い合わせいただきありがとうございます。当社のエンジニアがお客様のリクエストを確認したところ、現在出荷されているシステム構成では、必要な機能を実現するためのサポートされた方法はないと結論付けました。
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()
}
}
私はAVPlayerViewControllerをサブクラス化し、viewWillDisappearからの通知を投稿して、AVPlayerViewControllerの終了を示しています。
- (void) viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] postNotificationName:kPlayerViewDismissedNotification object:nil];
[super viewWillDisappear:animated];
}
これは100%正しくない可能性があります(AVPlayerViewControllerの上に別のビューが表示されていると失敗するため)が、AVPlayerViewControllerは常にスタックの一番上にあるため、うまくいきました。
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が閉じられている場合、または既に閉じられている場合は、ユーザーが[完了]ボタンをクリックしたと想定できます。
AVPLayerViewControllerをサブクラス化することでそれを行うことができます。ただし、これにより未定義の動作がいくつかあります。 (Apple docs。以下を参照)で示されます)また、AVPlayerViewControllerをサブクラス化してみましたが、メモリの問題に直面しました。
Apple Docs:によると
AVPlayerViewControllerをサブクラス化しないでください。このクラスのメソッドのオーバーライドはサポートされておらず、未定義の動作を引き起こします。
現在、AVPlayerViewControllerが閉じられた場合、コールバックは提供されません。 Apple開発者フォーラム:
Thread1 Apple=男は言う:
上記の推奨ジェスチャーメソッドを使用してAVPlayerViewControllerインスタンスの出口を手動で管理することは、プレーヤービューコントローラーが却下されたことを確認する最も信頼できる方法であると私はまだ信じています。
それが役に立てば幸い!
まあ、問題の修正があります。ボタンをAVPlayerViewControllerのサブビューとして追加できます。そうすることで、完了したボタンタップジェスチャを傍受できます。
誰も最も明白な解決策を導入したとは信じられません:通知を発行するか、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
他のソリューションについてコメントするには:
AVPlayer
のrate
が0.0f
になったことを確認し、AVPlayerViewController
のisBeingDismissed
を確認します。ただし、プレーヤーを事前に一時停止すると失敗します(もちろん代わりにタイマーで動作します)AVPlayerViewController
の-viewWillDisappear:
を使用:プレーヤーの上に何かが表示されると失敗する可能性があります。組み込みの字幕セレクタ。同様の解決策は、プレーヤーが以前に提示され、100%の時間動作する必要がある場合、提示するビューコントローラーの-viewWillAppear:
をチェックインすることです。これがあなたのユースケースに役立つかどうかはわかりませんが、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)
クリックを検出するためのコードを以下に示します。
アプリケーションデリゲートクラス内。ただし、そのビューで見つかったすべてのボタンを検出します。あなたはいくつかのコントロールを追加することができ、そのようなタイトルを確認してください。
-(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");
}
ここには完全な答えはないようですので、状況によっては、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
//*******
}
AVPlayerViewController
を表示するビューコントローラーAがある場合は、おそらくVC Aの内部でviewDidAppear/viewWillAppearをチェックインできます。これらが呼び出されるときはいつでも、少なくともAVPlayerViewControllerは表示されなくなったため、再生されません。