【问题标题】:MPMoviePlayerPlaybackDidFinishNotification being called again in iPhone 4.3 simulator when setting contentURL设置 contentURL 时在 iPhone 4.3 模拟器中再次调用 MPMoviePlayerPlaybackDidFinishNotification
【发布时间】:2012-07-02 14:45:53
【问题描述】:

注意:查看底部的更新。


我有一个应用程序可以从列表中逐一播放视频。所以,为了测试这个功能,我创建了一个只有一个视图控制器的简单应用程序。在实现this 视图控制器之前,我参考了这个博客。视图控制器名为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

如您所见,播放完成被调用了两次。这会导致从队列中跳过一个视频。 (其实在原项目中出现问题,nextVideo提前从服务器缓存了一个视频,如果缓存中存在则返回缓存视频的路径,否则返回nil .)。在这里,首先播放sintel_trailer.mp4。完成播放后,它会播放big_buck_bunny_trailer.mp4,而不是elephants_dream_trailer.mp4。也就是说,它会循环播放在其间跳过的视频。那么,是什么导致MPMoviePlayerPlaybackDidFinishNotification 调用两次?我正在为此工作两天,仍然没有运气。有什么想法吗?

更新 1:

目前我在回调moviePlayBackDidFinish: 中使用了一个开关,如下所示,并且正在工作:

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

但我仍然想知道是什么导致回调被调用两次。我觉得当前的 switch 解决方案就像一个 hack,并且喜欢将其删除。

更新 2:

直到现在,我一直在用 iPhone 4.3 模拟器尝试这个。但是,当我用 iPhone 5.0 模拟器和 iPhone 5.1 模拟器尝试相同的程序时,它可以正常工作。也就是说,电影播放完毕后只发送一个回调。这使我的小技巧(更新 1)变得无用(它解决了 4.3 中的问题,但在 5.0 和 5.1 中产生了问题)。我正在使用在 MacOSX Lion - 10.7.4 上运行的 Xcode 4.3.2。您对如何解决此问题有任何想法吗?为什么在 4.3 上有两个回调?

更新 3:

我查明线路导致问题。它在playNextVideo 方法中。导致问题的线路是_moviePlayer.contentURL = _movieUrl;。在第一个回调中更改它会导致再次发送 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....

【问题讨论】:

  • 我使用 AVQueuePlayer 解决了一个类似的问题,使用 AVAsset 和 AVPlayerItem 以更少的代码处理非常好的排队电影。
  • @Winston 目前,我正在使用时间技巧(请参阅更新的问题)。我确实看过AVQueuePlayerAVAssetAVPlayerItem,在这种情况下它似乎是一个不错的候选者。感谢您指出它们(我以前没有听说过它们)。不幸的是,那个项目已经结束了,我现在在做一个不同的项目。但是,从现在开始,我会强烈考虑在新项目中使用AVQueuePlayer
  • 很高兴能向您指出一些新的东西,感谢您的回复。

标签: iphone objective-c ios mpmediaplayercontroller


【解决方案1】:

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

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

代替

self.moviePlayer.contentURL = _movieUrl;

通知MPMoviePlayerPlaybackDidFinishNotification 将调用一次。

【讨论】:

    【解决方案2】:

    我遇到了同样的问题。并以这种方式解决:

    我创建了一种跳过视频的新方法

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

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

    在播放下一个视频(playNextVideo)之前,您必须致电

    [_moviePlayer prepareToPlay];
    

    这对我来说很好!

    【讨论】:

      猜你喜欢
      • 2012-03-09
      • 1970-01-01
      • 2010-09-17
      • 2012-04-26
      • 1970-01-01
      • 2011-09-07
      • 2012-11-03
      • 1970-01-01
      相关资源
      最近更新 更多