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

Tim*_*Tim 8 iphone mpmovieplayercontroller avaudioplayer avaudiorecorder ios

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

做这个的最好方式是什么?只有四个视频,每个视频大约两兆字节.

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

编辑

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

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

GSn*_*der 8

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

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

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

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

我做了一个小测试项目(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
Run Code Online (Sandbox Code Playgroud)

一半的代码只是为了观察播放器的状态,并确保在所有视频都被缓冲之后才开始播放.

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

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

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

2)除了视频帧之外没有任何用户界面,但我再次猜测这可能对您有利.


dan*_*anh 2

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