2012-07-02 37 views
5

LƯU Ý: Xem các cập nhật ở dưới cùng.MPMoviePlayerPlaybackDidFinishThông báo lại được gọi lại trong trình mô phỏng iPhone 4.3 khi cài đặt contentURL


Tôi có một ứng dụng để phát video từng người một từ danh sách. Vì vậy, để kiểm tra chức năng này, tôi đã tạo một ứng dụng đơn giản chỉ với một bộ điều khiển xem. Tôi đã tham chiếu blog này trước khi triển khai bộ điều khiển xem this. Bộ điều khiển xem được đặt tên TNViewController và thực hiện của nó là như sau:

#import <UIKit/UIKit.h> 
#import <MediaPlayer/MediaPlayer.h> 

@interface TNViewController : UIViewController { 
    @private 
    NSMutableArray *_videoArray; 
    int _currentVideo; 

    MPMoviePlayerController *_moviePlayer; 
    NSURL *_movieUrl; 
} 

@end 

thực hiện của nó là:

#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 

Bây giờ, vấn đề của tôi là thông báo MPMoviePlayerPlaybackDidFinishNotification được gọi là hai lần. Như bạn có thể thấy từ mã trên, tôi đã đăng ký chỉ một lần trong số viewDidLoad (trong initPlayer được gọi từ viewDidLoad). Đây là đầu ra nhật ký:

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 

Như bạn có thể thấy, quá trình phát lại kết thúc được gọi hai lần. Điều này khiến một video bị bỏ qua khỏi hàng đợi. (Trong thực tế, trong dự án ban đầu nơi sự cố xảy ra, nextVideo lưu trữ video trước từ máy chủ và trả về đường dẫn đến video được lưu trong bộ nhớ cache, nếu nó tồn tại trong bộ nhớ cache. Nếu không, nó sẽ trả về nil.). Ở đây, đầu tiên, sintel_trailer.mp4 được phát. Sau khi phát xong, thay vì elephants_dream_trailer.mp4, phát lại big_buck_bunny_trailer.mp4. Nghĩa là, nó sẽ phát các video bị bỏ qua ở giữa. Vì vậy, những gì đang gây ra các MPMoviePlayerPlaybackDidFinishNotification để gọi hai lần? Tôi đang làm việc này trong hai ngày, vẫn không có may mắn. Bất kỳ ý tưởng?

UPDATE 1:

Hiện nay tôi đang sử dụng một switch trong callback moviePlayBackDidFinish: như dưới đây và đang làm việc:

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

Nhưng tôi vẫn muốn biết những gì gây callback được gọi hai lần . Tôi cảm thấy giải pháp hiện tại của việc chuyển đổi như hack và muốn xóa nó.

UPDATE 2:

Cho đến bây giờ, tôi đã cố gắng này với iPhone 4.3 mô phỏng. Nhưng, khi tôi thử cùng một chương trình với trình giả lập iPhone 5.0 và trình mô phỏng iPhone 5.1, nó hoạt động mà không có bất kỳ vấn đề gì. Đó là, chỉ có một cuộc gọi lại đang được gửi sau khi bộ phim kết thúc phát. Và điều đó làm cho hack nhỏ của tôi (trên bản cập nhật 1) vô dụng (nó giải quyết vấn đề trong 4.3 nhưng tạo ra vấn đề ở 5.0 và 5.1). Tôi đang sử dụng Xcode 4.3.2 chạy trên MacOSX Lion - 10.7.4. Bạn có ý tưởng nào về cách giải quyết vấn đề này không? Tại sao hai cuộc gọi lại trên 4.3?

UPDATE 3:

tôi xác định đến dòng gây ra vấn đề. Đó là phương pháp playNextVideo. Dòng gây ra sự cố là _moviePlayer.contentURL = _movieUrl;. Thay đổi nó trong nguyên nhân gọi lại đầu tiên, MPMoviePlayerPlaybackDidFinishNotification sẽ được gửi lại. Nhưng, nó chỉ xảy ra trong trình giả lập iPhone 4.3. Bất kỳ ý tưởng?

CẬP NHẬT 4:

Tuy nhiên, tôi đã không có bất kỳ ý tưởng về hành vi kỳ lạ này. Vì vậy, bây giờ tôi đang sử dụng một thủ thuật thời gian như một trong UPDATE 1 như sau trên moviePlayBackDidFinish:

NSTimeInterval currentCallback = [NSDate timeIntervalSinceReferenceDate]; 
NSTimeInterval difference  = currentCallback - _lastCallback; 
_lastCallback     = currentCallback; 
if (difference < 5.0) { 
    return; 
} 
// code to play video.... 
+1

Tôi đã giải quyết vấn đề tương tự bằng cách sử dụng AVQueuePlayer với ít mã xử lý các phim xếp hàng thực sự tốt, với AVAsset và AVPlayerItem. – Winston

+0

@Winston Hiện tại, tôi đang đi với một thủ thuật thời gian (xem câu hỏi cập nhật). Tôi đã xem xét 'AVQueuePlayer', 'AVAsset' và' AVPlayerItem', và có vẻ như một ứng cử viên tốt trong trường hợp này. Cảm ơn bạn đã chỉ ra cho họ (tôi chưa từng nghe về họ trước đây). Thật không may, dự án đó đã kết thúc và hiện tôi đang ở một dự án khác. Nhưng, tôi sẽ mạnh mẽ xem xét sử dụng 'AVQueuePlayer' từ bây giờ cho các dự án mới. – Jomoos

+0

Tôi rất vui vì tôi có thể chỉ ra điều gì đó mới mẻ đối với bạn và tôi cảm ơn câu trả lời của bạn. – Winston

Trả lời

4

Tôi đã có cùng một vấn đề. và giải quyết nó theo cách này:

Tôi tạo ra một phương pháp mới để bỏ qua một đoạn video

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

Dừng các cầu thủ trong skipVideo sẽ gây ra một thông báo MPMovieFinishReasonPlaybackEnded (trong mô phỏng và trên thiết bị). Khi đặt contentUrl của trình phát bây giờ, không có thông báo MPMovieFinishReasonPlaybackEnded nào khác gây ra, do đó, moviePlayBackDidFinish chỉ được gọi một lần;

Trước khi chơi video tiếp theo (trong playNextVideo), bạn phải gọi

[_moviePlayer prepareToPlay]; 

đó làm việc tốt cho tôi!

1

Bạn có thể tạo máy nghe nhạc mới cho ca khúc tiếp theo:

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

Thay vì

self.moviePlayer.contentURL = _movieUrl; 

Notification MPMoviePlayerPlaybackDidFinishNotification sẽ gọi một lần.

Các vấn đề liên quan