在视频之间快速切换的正确方法

时间:2012-03-07 20:04:13

标签: iphone ios mpmovieplayercontroller avaudioplayer avaudiorecorder

我正在创建一个基于外部事件显示特定视频的应用程序,这可能需要播放视频快速更改 - 每秒一次或更多。但是,视频之间一定不能有差距或滞后。

最好的方法是什么?只有四个视频,每个视频大约两兆字节。

我正在考虑创建四个MPMoviePlayerControllers,并将其视图添加到主视图中但隐藏,并通过暂停和隐藏当前视频进行切换,然后取消隐藏并播放下一个视频。有更优雅的解决方案吗?

修改

以下是我的确切情况的更多信息:

  • 不同的视频帧共享大多数常见的像素 - 所以在切换过程中帧可以粘住,但不能显示黑帧。
  • 每个视频只有大约十秒钟,而且只有四个视频。一般状态转变为1 - 2 - 3 - 4 - > 1。
  • 视频播放应与同步AVAudioRecorder录制兼容。据我所知,MPMoviePlayerController不是。

2 个答案:

答案 0 :(得分:8)

您需要设置和预缓冲所有视频流以避免打嗝,所以我不认为您的多个MPMoviePlayerController解决方案太过分了。

但是,该特定解决方案可能存在问题,因为每个电影播放器​​都有自己的UI。 UI不会彼此同步,因此可能会显示控制栏,而另一个则不显示;一个可能处于全屏模式等。在它们之间切换会导致视觉不连续。

由于听起来您的视频切换不一定是用户启动的,我猜你不太关心标准的视频播放器用户界面。

我建议下到底层AV基金会。理论上,您可以为每个视频创建一个AVPlayerItem。这是一个没有UI开销的流管理对象,因此它非常适合您正在做的事情。然后,您可以 - 再次,理论上 - 创建一个AVPlayer和一个AVPlayerLayer来处理显示。当您想从一个AVPlayerItem流切换到另一个AVPlayerItem流时,您可以调用AVPlayer的replaceCurrentItemWithPlayerItem:消息来交换数据流。

我做了一点test project(GitHub)来验证这一点,不幸的是,直截了当的解决方案并不完美。没有视频流故障,但在从AVPlayer到AVPlayer的过渡中,表示层似乎会以适合下一部电影的大小短暂闪现上一部电影最后看到的帧。它似乎有助于为每部电影分配单独的AVPlayer对象,并在它们之间切换到一个恒定的播放器层。似乎仍然会瞬间闪现背景,但至少它是一个微妙的缺陷。这是代码的要点:

@interface ViewController : UIViewController
{
    NSMutableArray *players;
    AVPlayerLayer *playerLayer;
}

@property (nonatomic) IBOutlet UIView *videoView;

- (IBAction) selectVideo:(id)sender;

@end

@implementation ViewController

@synthesize videoView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSArray *videoTitles = [NSArray arrayWithObjects:@"Ultimate Dog Tease", 
        @"Backin Up", @"Herman Cain", nil];
    players = [NSMutableArray array];
    for (NSString *title in videoTitles) {
        AVPlayerItem *player = [AVPlayer playerWithURL:[[NSBundle mainBundle] 
            URLForResource:title withExtension:@"mp4"]];
        [player addObserver:self forKeyPath:@"status" options:0 context:nil];
        [players addObject:player];
    }
    playerLayer = [AVPlayerLayer playerLayerWithPlayer:[players objectAtIndex:0]];
    playerLayer.frame = self.videoView.layer.bounds;
    playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
    [self.videoView.layer addSublayer:playerLayer];
}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
    change:(NSDictionary *)change context:(void *)context
{
    [object removeObserver:self forKeyPath:@"status"];
    for (AVPlayer *player in players) {
         if (player.status != AVPlayerStatusReadyToPlay) {
             return;
         }
    }
    // All videos are ready to go
    [self playItemAtIndex:0];
}

- (void) playItemAtIndex:(NSUInteger)idx
{
    AVPlayer *newPlayer = [players objectAtIndex:idx];
    if (newPlayer != playerLayer.player) {
        [playerLayer.player pause];
        playerLayer.player = newPlayer;
    }
    [newPlayer play];
}

- (IBAction) selectVideo:(id)sender 
{
    [self playItemAtIndex:((UILabel *)sender).tag];
}

@end

只有一半的代码用于观察播放器的状态,并确保在所有视频都被缓冲后才开始播放。

分配三个独立的AVPlayerLayers(除了三个AVPlayers)可以防止任何类型的闪存。不幸的是,连接到未显示的AVPlayerLayer的AVPlayer似乎认为它不需要维护视频缓冲区。层之间的每个开关然后产生瞬态视频口吃。所以这不好。

使用AV Foundation时需要注意的几点是:

1)AVPlayer对象没有内置支持循环播放。您必须观察当前视频的结尾并手动寻找零时间。

2)除了视频帧之外没有任何用户界面,但我再次猜测这对你来说可能是一个优势。

答案 1 :(得分:2)

MPMoviePlayerController是一个单身人士。四个实例将共享相同的指针,相同的视图等。对于本机播放器,我认为您只有两个选择:一个是在您想要转换时更改contentURL属性。如果延迟这种方式是不可接受的,另一种选择是产生一个较长的视频连接较短的剪辑。通过设置currentPlaybackTime,您可以在单个较长的剪辑中创建非常快速的跳转。