NOTA: Consulte las actualizaciones en la parte inferior.MPMoviePlayerPlaybackDidFinishNotification se vuelve a llamar en el simulador de iPhone 4.3 al establecer contentURL
que tienen una aplicación para reproducir vídeos uno a uno de una lista. Entonces, para probar esta funcionalidad, creé una aplicación simple con solo un controlador de vista. Hice referencia a este blog antes de implementar el controlador de vista this. El controlador de vista se llama TNViewController
y su aplicación es la siguiente:
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
@interface TNViewController : UIViewController {
@private
NSMutableArray *_videoArray;
int _currentVideo;
MPMoviePlayerController *_moviePlayer;
NSURL *_movieUrl;
}
@end
Su implementación es:
#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
Ahora, mi problema es que la notificación MPMoviePlayerPlaybackDidFinishNotification
se llama dos veces. Como puede ver en el código anterior, me he registrado solo una vez en el viewDidLoad
(en initPlayer
llamado desde viewDidLoad
). Aquí está la salida del registro:
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
Como se puede ver, la reproducción terminado se llama dos veces. Esto hace que se salte un video de la cola. (De hecho, en el proyecto original donde se produce el problema, nextVideo
guarda en caché un video por adelantado del servidor y devuelve la ruta al video en caché, si existe en la caché. De lo contrario, devuelve nil
.). Aquí, primero se reproduce el sintel_trailer.mp4
. Una vez que finaliza la reproducción, en lugar de elephants_dream_trailer.mp4
, reproduce big_buck_bunny_trailer.mp4
. Es decir, ciclos reproduce los videos omitiendo mientras tanto. Entonces, ¿qué está causando que el MPMoviePlayerPlaybackDidFinishNotification
invoque dos veces? Estoy trabajando en esto durante dos días, todavía sin suerte. ¿Alguna idea?
ACTUALIZACIÓN 1:
Actualmente estoy usando un interruptor en la devolución de llamada moviePlayBackDidFinish:
, como a continuación y está trabajando:
if (!_playNextVideo) {
_playNextVideo = YES;
return;
}
_playNextVideo = NO;
// code to play video....
Pero aún así me gustaría saber lo que hace que la devolución de llamada se llama dos veces . Siento la solución actual de cambio como un truco, y me gusta eliminarlo.
ACTUALIZACIÓN 2:
Hasta ahora, he estado tratando esto con iPhone 4.3 simulador. Pero, cuando probé el mismo programa con el simulador de iPhone 5.0 y el simulador de iPhone 5.1, funciona sin ningún problema. Es decir, solo se envía una devolución de llamada después de que la película finalizó la reproducción. Y eso hace que mi pequeño truco (en la actualización 1) sea inútil (resuelve el problema en 4.3 pero crea un problema en 5.0 y 5.1). Estoy usando Xcode 4.3.2 ejecutándose en MacOSX Lion - 10.7.4. ¿Tienes alguna idea sobre cómo resolver este problema? ¿Por qué dos devoluciones de llamada en 4.3?
Actualización 3:
que Pinpoint a la línea causa un problema. Está en el método playNextVideo
. La línea causa problema es _moviePlayer.contentURL = _movieUrl;
. Cambiarlo en la primera devolución de llamada hace que se vuelva a enviar el MPMoviePlayerPlaybackDidFinishNotification
. Pero sucede solo en el simulador de iPhone 4.3. ¿Alguna idea?
ACTUALIZACIÓN 4:
Aún así, no tengo ninguna idea sobre este comportamiento extraño. Por lo tanto, ahora estoy utilizando un truco tiempo como el de ACTUALIZACIÓN 1 como sigue en moviePlayBackDidFinish:
NSTimeInterval currentCallback = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval difference = currentCallback - _lastCallback;
_lastCallback = currentCallback;
if (difference < 5.0) {
return;
}
// code to play video....
Resolví un problema similar con el AVQueuePlayer que con menos código maneja películas realmente en cola, con AVAsset y AVPlayerItem. – Winston
@ Winston Actualmente, voy con un truco de tiempo (vea la pregunta actualizada). Eché un vistazo a 'AVQueuePlayer',' AVAsset' y 'AVPlayerItem', y parece ser un buen candidato en este caso. Gracias por señalarlos (no he oído hablar de ellos antes). Desafortunadamente, ese proyecto ha terminado, y ahora estoy en un proyecto diferente. Pero, consideraré usar 'AVQueuePlayer' a partir de ahora en nuevos proyectos. – Jomoos
Me alegro de poder señalarle algo nuevo y le agradezco su respuesta. – Winston