iPhone开发mpmovieplayer崩溃问题

12

我正在开发一个应用程序,可以让我使用iPhone远程在iPad上播放不同的视频。我一直按照苹果的示例来制作视频播放器,但是遇到了一些问题。视频播放得很好,我可以从各种视频中播放它,但是在多次切换视频之后,它会崩溃,并在调试器中显示以下内容:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An        AVPlayerItem cannot be associated with more than one instance of AVPlayer'
*** First throw call stack:
(0x380da8bf 0x37c261e5 0x30acbcb5 0x30abc1f7 0x30ac3bf3 0x30c93d55 0x30c95f7b 0x380ad2dd   0x380304dd 0x380303a5 0x37e07fcd 0x31bb0743 0x25e5 0x257c)

这是我用来创建播放器的代码:

MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
    [self setMoviePlayerController:player];
    [self installMovieNotificationObservers];
    [player setContentURL:movieURL];
    [player setMovieSourceType:sourceType];
    [self applyUserSettingsToMoviePlayer];
    [self.view addSubview:self.backgroundView];
    [player.view setFrame:self.view.bounds];
    [player.view setBackgroundColor = [UIColor blackColor];
    [self.view addSubview:player.view];
}

而当当前电影停止时,我使用:

[[self moviePlayerController] stop];

MPMoviePlayerController *player = [self moviePlayerController];
[player.view removeFromSuperview];

[self removeMovieNotificationHandlers];
[self setMoviePlayerController:nil];

编辑: 我现在发现每次我尝试第11次切换视频时都会发生这种情况,很奇怪!我简直要抓狂了。


当我使用AirPlay播放视频并允许应用程序被推到后台时,我遇到了完全相同的错误。偶尔,当应用程序被带回前台时,如果包含播放器的视图已被卸载,则尝试恢复应用程序会以此确切的错误崩溃。 - reddersky
有解决方案了吗?我在现场也遇到了同样的错误。我的应用程序是一个视频应用程序,可以从网络加载不同的视频。我同时有多个MPMovieControllers处于活动状态,但任何时候只有一个正在播放。 - Joris Weimar
只是为了增加一些复杂性。在我的情况下,我希望OP的情况也是如此,这个错误会在“_serverConnectionDiedNotification”被记录后立即触发。详细信息如下:Info--notification=Error Domain=AVFoundationErrorDomain Code=-11819 "无法完成操作" UserInfo=0x42df3e0 {NSLocalizedRecoverySuggestion=稍后再试。, NSLocalizedDescription=无法完成操作}。开始悬赏!各位加油! - Dev Kanchen
8个回答

6
我解决这个问题的方法是,在进行setContentURL之前停止MPMoviePlayerController。
    MPMoviePlayerController *streamPlayer;

    [streamPlayer stop];
    [streamPlayer setContentURL:[NSURL URLWithString:selectedStation]];

1
我曾经遇到过完全相同的问题。 我的代码没有问题,我猜你的代码也没有问题 :) 只是我的视频文件损坏了。 将 *.mov 类型更改为 m4a 之类的格式就可以解决问题。也许你播放的一个或多个文件已经损坏了? 试着找出哪些文件导致了崩溃,然后如果可以的话,在播放时快速向前或向后调整其中一个文件的播放位置 - 这应该会在几次尝试后导致崩溃。 这就是我如何找到坏文件的方法。顺便说一下,我所有的坏文件都是用 Snapz Pro X 制作的电影 .mov :)

有趣。我会去检查一下。 - Dev Kanchen
这似乎是正确的。在删除一些随机来源的测试视频等内容后,我们已经停止看到崩溃,但崩溃的“共同症状”是高CPU使用率和应用程序在开始播放视频时冻结。我们也解决了高CPU问题,但现在不清楚是什么导致了问题以及究竟是什么解决了它。我们只是到处尝试,可能得到了解决。但再测试一周就可以证明其中至少一种方法有效。可惜我还没有找到延长赏金的方法。让我看看。 - Dev Kanchen
顺便说一下,我们试图从服务器播放m3u8文件,并且直到上周初都没有遇到任何问题。这些视频都是从同一个客户端获取的MP4格式,因此高度可能是使用相同设置录制和编码的。我们确切知道它们都是1080p,平均码率约为8-10mbps。也许所有这些都指向了一个内容问题 - 因为上周是我们第一次上传一些随机视频来测试系统。而且视频播放/服务代码在过去几周内都没有更改。 - Dev Kanchen
P.s. CPU使用率修复是我们在数周内对播放代码进行的第一次更改,而且这是在我们面对问题之后才进行的。 - Dev Kanchen
你知道吗,我宁愿授予悬赏,也不想让这些点数浪费了。谢谢Asen。为了完整起见,我会回复我们的测试结果,以便未来的开发人员在寻找答案时能够获得准确的数据。 - Dev Kanchen
显示剩余2条评论

1

你应该保留 moviePlayerController,如果你想播放另一个视频,只需使用

[self.moviePlayerController setContentURL:movieURL];

然后在您的通知回调函数中:

- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
    self.moviePlayer = nil;
    [self initanothermovieplayerandplay];
}

请不要从通知中心中删除通知处理程序,只在您的 VC 的 dealloc 方法中执行此操作。

现在让我们在电影播放完成时添加一些淡入淡出效果:

- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
    [UIView animateWithDuration:1
                      delay: 0.0
                    options: UIViewAnimationOptionCurveEaseIn
                 animations:^{
                     // one second to fade out the view
                     self.moviePlayer.view.alpha = 0.0;
                 }
                 completion:^(BOOL finished){
                       self.moviePlayer = nil;
                       [self initanothermovieplayerandplay];
                 }
}

当然!除了setContentURL之外,我还添加了prepareToPlay,以便自动播放。如果我不加prepare...它就不会自动播放。问题是,正如@Jeff B所说,这并不总是发生。只有在几次调用后才会出现此错误。 - ferostar
@ferostar,我在答案中添加了一些代码,请告诉我是否有帮助。 - Allen
谢谢你的代码!但我相信除了将它变成nil并重新创建之外,一定还有其他的方法。MPMovie应该能够在视频之间平稳过渡,不需要像这样做,你觉得呢? - ferostar
将movieplayer实例设置为nil非常重要,正如Allen所说 - 这单独解决了我的问题,即我甚至没有尝试重用播放器。 - shawnwall

1
在您上面的实现中,ARC 不知道 MPMoviePlayerController 已经完成并需要被释放。
在您的 .h 文件中定义 MPMoviePlayerController,并通过 @property(和 @synthesize)使其可访问。
@property (strong, nonatomic) MPMoviePlayerController * moviePlayerController;

然后将您的alloc和init的结果分配给它。例如:

self.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];

即使我启用了ARC,当我尝试放置一个release时,它仍然会提示在ARC模式下不允许。我应该关闭ARC吗? - Jeff B
该死... ARC。我还在适应中。你需要让你的父视图控制器显式地保留你的MPMoviePlayer,然后ARC就会知道该怎么处理它了。我会修改我的答案来反映这一点。 - Michael Dautermann
仍然没有运气 :-( 是否尝试在另一个视频停止之前添加视频?只有在它们之间多次切换后才会发生这种情况,直到那时一切都正常。我启动了工具来检查泄漏,但没有发现任何问题。是否还有其他工具可以用来调试此问题?我已启用NSZombieEnabled,但这并没有帮助,我猜它没有释放已经被释放的东西。 - Jeff B

0

这个错误似乎有很多不同的原因,但我发现的原因是当你以特定的顺序调用方法时,MPMoviePlayerController类会出现问题。来自IRC频道的消息:

“显然,如果您在设置源类型和尚未设置视图的情况下调用prepareToPlay,则会导致崩溃”

所以我通过确保我最后调用prepareToPlay:(或者倒数第二个,最后一个是play:)来解决了这个问题。

这也很奇怪,因为我的原始代码在iOS 5.1中工作正常,但当我开始使用iOS 6.0 sdk时,这个问题突然出现了。这可能是MPMoviePlayerController代码中的一个bug,所以我将在此提交一个radar报告,因为在设置视图/设置sourceFileType之前调用prepareToPlay:不应该抛出异常(或者至少与实际错误似乎没有任何关系的异常)。


0

不确定这里是否是这种情况,但我们遇到了很多问题,因为MPMoviePlayer在某个地方是单例。 我们实现了自己的MoviePlayer包装器,可以从UIView中使用(实际上我们只有一个UIView子类MoviePlayerView来显示电影),并确保只存在一个MPMoviePlayerController实例。代码如下(它包含一些特殊的东西,我们需要按照我们想要的方式显示预览/缩略图等等,你应该清理一下以及一些释放语句):

//  MoviePlayer.h

#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>
#import "Logger.h"

@class MoviePlayerView;

@interface MoviePlayer : NSObject
{
  @private
  MPMoviePlayerController *controller;
  MoviePlayerView *currentView;
}

@property (nonatomic, readonly) MPMoviePlayerController *controller;

+(MoviePlayer *) instance;

-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view;
-(void) stopMovie;

@end



//  MoviePlayer.m




#import "MoviePlayer.h"
#import "MoviePlayerView.h"

@implementation MoviePlayer

@synthesize controller;

static MoviePlayer *player = nil;

#pragma mark Singleton management

+(MoviePlayer *) instance
{
  @synchronized([MoviePlayer class])
  {
    if (player == nil) 
    {
      player = [[super allocWithZone:NULL] init];
      player->controller = [[MPMoviePlayerController alloc] init];
      player->controller.shouldAutoplay = NO;
      player->controller.scalingMode = MPMovieScalingModeAspectFit;
      player->currentView = nil;
    }
    return player;
  }
}

+(id) allocWithZone:(NSZone *)zone
{
  return [[self instance] retain];
}

-(id) copyWithZone:(NSZone *)zone
{
  return self;
}

-(id) retain
{
  return self;
}

-(NSUInteger) retainCount
{
  return NSUIntegerMax; 
}

-(oneway void) release
{
  // singleton will never be released
}

-(id) autorelease
{
  return self;
}

#pragma mark MoviePlayer implementations

-(void) stopMovie
{
  @synchronized(self)
  {
    if (controller.view.superview)
    {
      [controller.view removeFromSuperview];
    }
    if (controller.playbackState != MPMoviePlaybackStateStopped)
    {
      [controller pause];
      [controller stop];
    }
    if (currentView)
    {
      NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
      [ntfc removeObserver:currentView name:MPMoviePlayerLoadStateDidChangeNotification object:controller];
      [ntfc removeObserver:currentView name:MPMoviePlayerPlaybackStateDidChangeNotification object:controller];
      currentView = nil;
    }
  }
}

-(void) playMovie:(NSURL*)movieURL onView:(MoviePlayerView *)view
{
  @synchronized(self)
  {
    [self stopMovie];
    currentView = view;

    NSNotificationCenter *ntfc = [NSNotificationCenter defaultCenter];
    [ntfc addObserver:currentView 
             selector:@selector(loadStateDidChange:)
                 name:MPMoviePlayerLoadStateDidChangeNotification 
               object:controller];
    [ntfc addObserver:currentView 
             selector:@selector(playbackStateDidChange:)
                 name:MPMoviePlayerPlaybackStateDidChangeNotification
               object:controller];

    [controller setContentURL:movieURL];
    controller.view.frame = view.bounds;
    [view addSubview: controller.view];
    [controller play];
  }
}

@end


//  MoviePlayerView.h

#import <UIKit/UIKit.h>
#import "MoviePlayer.h"


@interface MoviePlayerView : MediaView 
{
  NSURL *movieURL;
  NSURL *thumbnailURL;
  UIImageView *previewImage;
  UIView *iconView;
  BOOL hasPreviewImage;
}

-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie;
-(void) loadStateDidChange:(NSNotification *)ntf;
-(void) playbackStateDidChange:(NSNotification *)ntf;

@end



//  MoviePlayerView.m

#import "MoviePlayerView.h"

@interface MoviePlayerView()
-(void) initView;
-(void) initController;
-(void) playMovie;
-(void) setActivityIcon;
-(void) setMovieIcon:(float)alpha;
-(void) clearIcon;
-(CGPoint) centerPoint;
@end

@implementation MoviePlayerView

-(id) initWithFrame:(CGRect)frame thumbnailURL:(NSURL *)thumbnail movieURL:(NSURL *)movie
{
  self = [super initWithFrame:frame];
  if (self) 
  {
    movieURL = [movie retain];
    thumbnailURL = [thumbnail retain];
    [self initView];
    [self initController];
    hasPreviewImage = NO;
    loadingFinished = YES;
  }
  return self;
}

-(void) dealloc
{
  [iconView release];
  [previewImage release];
  [movieURL release];
  [super dealloc];
}

-(void)initView
{
  self.backgroundColor = [UIColor blackColor];

  // add preview image view and icon view
  previewImage = [[UIImageView alloc] initWithFrame:self.bounds];
  [previewImage setContentMode:UIViewContentModeScaleAspectFit];
  UIImage *img = nil;
  if (thumbnailURL)
  {
    img = [ImageUtils loadImageFromURL:thumbnailURL];
    if (img)
    {
      previewImage.image = img;
      hasPreviewImage = YES;
    }
  }
  [self addSubview:previewImage];
  [self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
}

-(void)initController
{
  UITapGestureRecognizer *rec = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(playMovie)];
  [self addGestureRecognizer:rec];
  [rec release];
}

-(void)playMovie
{
  [[MoviePlayer instance] playMovie:movieURL onView:self];
  [self setActivityIcon];
}

-(void) loadStateDidChange:(NSNotification *)ntf
{
  MPMoviePlayerController *controller = [ntf object];
  switch (controller.loadState)
  {
    case MPMovieLoadStatePlayable:
    {
      [self clearIcon];
      [controller setFullscreen:YES animated:YES];
      break;
    }
    case MPMovieLoadStateStalled:
    {
      [self setActivityIcon];
      break;
    }
    default:
    {
      break; // nothing to be done
    }
  }
}

-(void) playbackStateDidChange:(NSNotification *)ntf
{
  MPMoviePlayerController *controller = [ntf object];
  switch (controller.playbackState) 
  {
    case MPMoviePlaybackStatePlaying:
    {
      [self clearIcon];
      break;
    }
    case MPMoviePlaybackStateStopped:
    {
      [self setMovieIcon:(hasPreviewImage ? 0.8f : 0.3f)];
      break;
    }
    case MPMoviePlaybackStatePaused:
    {
      [self setMovieIcon:0.8f];
      break;
    }
    case MPMoviePlaybackStateInterrupted:
    {
      [self setActivityIcon];
      break;
    }
    default:
    {
      break; // nothing to be done
    }
  }
}

-(void) setActivityIcon
{
  [self clearIcon];
  iconView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  iconView.center = [self centerPoint];
  [self addSubview:iconView];
  [iconView performSelector:@selector(startAnimating)];
}

-(void) setMovieIcon:(float)alpha
{
  [self clearIcon];
  iconView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"icon_movie.png"]];
  iconView.center = [self centerPoint];
  iconView.alpha = alpha;
  [self addSubview:iconView];
}

-(void) clearIcon
{
  if (iconView)
  {
    SEL stop = @selector(stopAnimating);
    if ([iconView respondsToSelector:stop])
    {
      [iconView performSelector:stop];
    }
    [iconView removeFromSuperview];
    [iconView release];
    iconView = nil;
  }
}

-(CGPoint) centerPoint
{
  return CGPointMake(roundf(self.bounds.size.width / 2.0f), roundf(self.bounds.size.height / 2.0f));
}

-(void)resize
{
  for (UIView *view in [self subviews]) 
  {
    if (view == iconView)
    {
      iconView.center = [self centerPoint];
      continue;
    }
    view.frame = self.bounds;
  }
  [self addCaptionLabel];
}

-(void) layoutSubviews
{
  [super layoutSubviews];
  [self resize];
}

@end

0

我曾经遇到过同样的问题。我的解决方案是使用prepareToPlay:

MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];

if (player) {
   [player prepareToPlay];
   //...
}

0
...
player = [[MPMoviePlayerController alloc] initWithContentURL: [NSURL URLWithString:...
...

但我没有给手机(Wi-Fi)提供互联网连接 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接