Сбой разработки iPhone mpmovieplayer
Я работаю над приложением, которое позволит мне воспроизводить разные видео на iPad удаленно с iPhone. Я следил за примерами с яблоками для видеоплеера, но у меня были некоторые проблемы. Воспроизведение видео очень хорошо, и я могу заставить его играть из разных видео, но переключение между ними несколько раз приведет к сбою, и я получаю это в отладчике:
*** 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-й раз. странно! Я практически вытягиваю свои волосы.
Ответы
Ответ 1
Для меня эта проблема была остановлена с помощью MPMoviePlayerController перед выполнением setContentURL.
MPMoviePlayerController *streamPlayer;
[streamPlayer stop];
[streamPlayer setContentURL:[NSURL URLWithString:selectedStation]];
Ответ 2
В реализации, описанной выше, ARC не знает, что MPMoviePlayerController закончен и должен быть выпущен.
Определите MPMoviePlayerController в вашем .h файле и сделайте его доступным через @property (и @synthesize).
@property (strong, nonatomic) MPMoviePlayerController * moviePlayerController;
Затем возьмите результат вашего alloc и init и назначьте ему это. То есть.
self.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
Ответ 3
вам следует просто сохранить moviePlayerController, и если вы хотите воспроизвести другое видео, просто используйте
[self.moviePlayerController setContentURL:movieURL];
то в вашем обратном вызове уведомления:
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
self.moviePlayer = nil;
[self initanothermovieplayerandplay];
}
и не удаляйте обработчик уведомлений из центра уведомлений, делайте это только в методе dealloc вашего VC.
теперь добавьте некоторое затухание при выполнении фильма:
- (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];
}
}
Ответ 4
У меня была точно такая же проблема.
Ничего не случилось с моим, и я догадываюсь с вашим кодом:)
Просто сломанный видеофайл был моей проблемой.
Изменение типа *.mov на m4a, например, исправлено. Может быть, один или несколько файлов, которые вы играете, повреждены?
Попытайтесь выяснить, какие файлы приводят к сбою, и если вы можете попытаться быстро продвинуть назад позицию игры одного из них во время игры - это должно привести к сбою в нескольких попытках.
Вот как я нашел плохие файлы. Кстати, все мои плохие файлы были фильмами .mov сделаны с Snapz Pro X:)
Ответ 5
Не уверен, что это так, но у нас было много проблем, потому что 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
Ответ 6
...
player = [[MPMoviePlayerController alloc] initWithContentURL: [NSURL URLWithString:...
...
но я не подключил интернет к телефону (wi-fi):)
Ответ 7
У меня была та же проблема. Мое решение использует prepareToPlay:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentOfURL:movieURL];
if (player) {
[player prepareToPlay];
//...
}
Ответ 8
Эта ошибка, похоже, выбрасывается по разным причинам, но причина, по которой я обнаружил, заключается в том, что класс MPMoviePlayerController запутывается, если вы вызываете методы в определенном порядке. От канала IRC:
", по-видимому, если вы вызовете setupToPlay WHILE, задав тип источника и НЕ установленное представление еще вызывает этот сбой"
Поэтому я исправил это, просто удостоверившись, что я назвал prepareToPlay:
LAST (или второй, чтобы последний, с последним play:
).
Это также странно, потому что мой исходный код работал в iOS 5.1, но эта проблема внезапно проявилась, когда я начал использовать sdk iOS 6.0. Возможно, это ошибка в коде MPMoviePlayerController, поэтому я собираюсь подать на него радарный отчет, как вызов prepareToPlay:
перед установкой представления/параметра sourceFileType
не должен генерировать исключение (или, по крайней мере, исключение что, по-видимому, не имеет ничего общего с фактической ошибкой)