在iPhone 4.3模拟器中再次调用MPMoviePlayerPlaybackDidFinishNotification时设置contentURL

注意:请参阅底部的更新。


我有一个应用程序从列表中逐一播放video。 所以,为了testing这个function,我只用一个视图控制器创build了一个简单的应用程序。 在实现这个视图控制器之前我引用了这个博客 视图控制器被命名为TNViewController ,其实现如下:

 #import <UIKit/UIKit.h> #import <MediaPlayer/MediaPlayer.h> @interface TNViewController : UIViewController { @private NSMutableArray *_videoArray; int _currentVideo; MPMoviePlayerController *_moviePlayer; NSURL *_movieUrl; } @end 

其实施是:

 #import "TNViewController.h" @implementation TNViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [[UIApplication sharedApplication] setStatusBarHidden:YES animated:NO]; [self.view setFrame:CGRectMake(0, 0, 480, 320)]; [self initVideos]; [self initPlayer]; } - (void) initVideos { _videoArray = [[NSMutableArray alloc] init]; NSString *path = [[NSBundle mainBundle] pathForResource:@"sintel_trailer" ofType:@"mp4" inDirectory:nil]; [_videoArray addObject:path]; path = [[NSBundle mainBundle] pathForResource:@"elephants_dream_trailer" ofType:@"mp4" inDirectory:nil]; [_videoArray addObject:path]; path = [[NSBundle mainBundle] pathForResource:@"big_buck_bunny_trailer" ofType:@"mp4" inDirectory:nil]; [_videoArray addObject:path]; _currentVideo = -1; } - (NSString*) nextVideo { _currentVideo++; if (_currentVideo >= _videoArray.count) { _currentVideo = 0; } return [_videoArray objectAtIndex:_currentVideo]; } - (void) initPlayer { _moviePlayer = [[MPMoviePlayerController alloc]init]; [self readyPlayer]; [self.view addSubview:_moviePlayer.view]; // Register to receive a notification when the movie has finished playing. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayBackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:_moviePlayer]; } - (void) readyPlayer { _movieUrl = [NSURL fileURLWithPath:[self nextVideo]]; [_movieUrl retain]; _moviePlayer.contentURL = _movieUrl; // For 3.2 devices and above if ([_moviePlayer respondsToSelector:@selector(loadState)]) { // Set movie player layout [_moviePlayer setControlStyle:MPMovieControlStyleNone]; [_moviePlayer setFullscreen:YES]; // May help to reduce latency [_moviePlayer prepareToPlay]; // Register that the load state changed (movie is ready) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayerLoadStateChanged:) name:MPMoviePlayerLoadStateDidChangeNotification object:nil]; } else { // Register to receive a notification when the movie is in memory and ready to play. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePreloadDidFinish:) name:MPMoviePlayerContentPreloadDidFinishNotification object:nil]; } } /*--------------------------------------------------------------------------- * For 3.1.x devices *--------------------------------------------------------------------------*/ - (void) moviePreloadDidFinish:(NSNotification*)notification { // Remove observer [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerContentPreloadDidFinishNotification object:nil]; // Play the movie [_moviePlayer play]; } /*--------------------------------------------------------------------------- * For 3.2 and 4.x devices *--------------------------------------------------------------------------*/ - (void) moviePlayerLoadStateChanged:(NSNotification*)notification { NSLog(@"moviePlayerLoadStateChanged"); // Unless state is unknown, start playback if ([_moviePlayer loadState] != MPMovieLoadStateUnknown) { // Remove observer [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerLoadStateDidChangeNotification object:nil]; // Set frame of movie player [[_moviePlayer view] setFrame:CGRectMake(0, 0, 480, 320)]; // Play the movie [_moviePlayer play]; } } - (void) moviePlayBackDidFinish:(NSNotification*)notification { NSLog(@"playback finished..."); NSLog(@"End Playback Time: %f", _moviePlayer.endPlaybackTime); int reason = [[[notification userInfo] valueForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] intValue]; if (reason == MPMovieFinishReasonPlaybackEnded) { NSLog(@"Reason: movie finished playing"); }else if (reason == MPMovieFinishReasonUserExited) { NSLog(@"Reason: user hit done button"); }else if (reason == MPMovieFinishReasonPlaybackError) { NSLog(@"Reason: error"); } [self playNextVideo]; } - (void) playNextVideo { NSString *filePath = [self nextVideo]; [_movieUrl release]; _movieUrl = [NSURL fileURLWithPath:filePath]; [_movieUrl retain]; _moviePlayer.contentURL = _movieUrl; [_moviePlayer play]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationLandscapeRight); } - (void) dealloc { [_moviePlayer release]; [_movieUrl release]; [_videoArray release]; [super dealloc]; } @end 

现在,我的问题是通知MPMoviePlayerPlaybackDidFinishNotification被调用两次。 从上面的代码可以看出,我只在viewDidLoad (在initPlayer调用viewDidLoad )中注册了它。 这里是日志输出:

 2012-07-02 12:29:17.661 DemoApp[1191:ef03] moviePlayerLoadStateChanged 2012-07-02 12:30:11.470 DemoApp[1191:ef03] playback finished... 2012-07-02 12:30:11.471 DemoApp[1191:ef03] End Playback Time: -1.000000 2012-07-02 12:30:11.472 DemoApp[1191:ef03] Reason: movie finished playing 2012-07-02 12:30:11.474 DemoApp[1191:ef03] playback finished... 2012-07-02 12:30:11.475 DemoApp[1191:ef03] End Playback Time: -1.000000 2012-07-02 12:30:11.476 DemoApp[1191:ef03] Reason: movie finished playing 2012-07-02 12:31:03.821 DemoApp[1191:ef03] playback finished... 2012-07-02 12:31:03.822 DemoApp[1191:ef03] End Playback Time: -1.000000 2012-07-02 12:31:03.824 DemoApp[1191:ef03] Reason: movie finished playing 2012-07-02 12:31:03.826 DemoApp[1191:ef03] playback finished... 2012-07-02 12:31:03.827 DemoApp[1191:ef03] End Playback Time: -1.000000 2012-07-02 12:31:03.827 DemoApp[1191:ef03] Reason: movie finished playing 

正如你所看到的,播放结束被称为两次。 这会导致一个video从队列中跳过。 ( 事实上,在发生问题的原始项目中, nextVideo预先从服务器caching一个video, nextVideocachingvideo的path返回给caching,否则返回nil )。 在这里,首先播放sintel_trailer.mp4 。 完成播放后,而不是elephants_dream_trailer.mp4播放big_buck_bunny_trailer.mp4 。 也就是说,它循环播放跳过的video。 那么,是什么导致MPMoviePlayerPlaybackDidFinishNotification调用两次? 我在这工作了两天,仍然没有运气。 任何想法?

更新1:

目前我在callbackmoviePlayBackDidFinish:使用了一个开关moviePlayBackDidFinish:像下面这样工作:

 if (!_playNextVideo) { _playNextVideo = YES; return; } _playNextVideo = NO; // code to play video.... 

但我仍然想知道是什么导致callback被调用两次。 我感觉当前的解决scheme就像一个黑客,并喜欢将其删除。

更新2:

直到现在,我一直在用iPhone 4.3模拟器来尝试这个。 但是,当我用iPhone 5.0模拟器和iPhone 5.1模拟器尝试相同的程序,它没有任何问题。 也就是说,电影完成播放后,只有一个callback正在发送。 这使我的小黑客(更新1)无用(它解决了4.3中的问题,但在5.0和5.1中产生了问题)。 我正在使用在MacOSX Lion – 10.7.4上运行的Xcode 4.3.2。 你有什么想法如何解决这个问题? 为什么两个callback4.3?

更新3:

我find线路导致问题。 它是在playNextVideo方法。 该行导致问题是_moviePlayer.contentURL = _movieUrl; 。 在第一个callback中改变它, MPMoviePlayerPlaybackDidFinishNotification被再次发送。 但是,它只发生在iPhone 4.3模拟器。 任何想法?

更新4:

不过,我对这种奇怪的行为还没有任何想法。 所以,我现在正在使用像UPDATE 1中的一个时间技巧如下moviePlayBackDidFinish:

 NSTimeInterval currentCallback = [NSDate timeIntervalSinceReferenceDate]; NSTimeInterval difference = currentCallback - _lastCallback; _lastCallback = currentCallback; if (difference < 5.0) { return; } // code to play video.... 

我有同样的问题。 并通过这种方式解决了它:

我创build了一个跳过video的新方法

 - (void) skipVideo {  [_moviePlayer stop]; } 

skipVideo停止播放器将导致MPMovieFinishReasonPlaybackEnded通知(在模拟器和设备上)。 现在设置播放器的contentUrl时,不会引起其他的MPMovieFinishReasonPlaybackEnded通知,所以moviePlayBackDidFinish只被调用一次;

在播放下一个video(在播放下一个video)之前,你必须打电话

 [_moviePlayer prepareToPlay]; 

这对我来说很好!

您可以为下一首曲目创build新的播放器:

 MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL: _movieUrl]; if (player) { [self setMoviePlayer:player]; } [self.moviePlayer play]; 

代替

 self.moviePlayer.contentURL = _movieUrl; 

通知MPMoviePlayerPlaybackDidFinishNotification将被调用一次。