2012-07-02 6 views
5

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.... 
+1

Resolví un problema similar con el AVQueuePlayer que con menos código maneja películas realmente en cola, con AVAsset y AVPlayerItem. – Winston

+0

@ 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

+0

Me alegro de poder señalarle algo nuevo y le agradezco su respuesta. – Winston

Respuesta

4

que tenía el mismo problema. y resuelto de esta manera:

he creado un nuevo método para saltarse un video

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

Detener el reproductor de skipVideo provocará una notificación MPMovieFinishReasonPlaybackEnded (en el simulador y en el dispositivo). Cuando se configura contentUrl del reproductor ahora, no se produce ninguna otra notificación de MPMovieFinishReasonPlaybackEnded, por lo que moviePlayBackDidFinish se invoca una sola vez;

Antes de jugar al lado de vídeo (en playNextVideo) tienes que llamar

[_moviePlayer prepareToPlay]; 

que funciona bien para mí!

1

Puede crear nuevo jugador de pista siguiente:

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

En lugar de

self.moviePlayer.contentURL = _movieUrl; 

Notificación MPMoviePlayerPlaybackDidFinishNotification será llamada una vez.

Cuestiones relacionadas