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

Posted

技术标签:

【中文标题】在视频之间快速切换的正确方法【英文标题】:Proper way to quickly switch between videos 【发布时间】:2012-03-07 20:04:12 【问题描述】:

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

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

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

编辑

这里有一些关于我的确切情况的更多信息:

不同的视频帧大部分共享相同的像素 - 因此在切换过程中帧卡住是可以的,但出现黑帧是不行的。 每个视频只有十秒左右,而且只有四个视频。一般的状态转换是 1234->1。 视频播放应该与同步 AVAudioRecorder 录制兼容。据我所知,MPMoviePlayerController 不兼容。

【问题讨论】:

【参考方案1】:

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

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

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

我建议下降到底层,AV Foundation。理论上,您可以为每个视频创建一个 AVPlayerItem。这是一个没有 UI 开销的流管理对象,因此非常适合您正在做的事情。然后,您可以再次在理论上创建一个 AVPlayer 和一个 AVPlayerLayer 来处理显示。当你想从一个 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(除了三个 AVPlayer)可以防止任何类型的闪存。不幸的是,连接到未显示的 AVPlayerLayer 的 AVPlayer 似乎假定它不需要维护视频缓冲区。然后层之间的每次切换都会产生短暂的视频卡顿。所以这不好。

使用 AV Foundation 时需要注意以下几点:

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

2) 除了视频帧之外,根本没有任何 UI,但我再次猜测,这实际上对您来说可能是一个优势。

【讨论】:

这很有帮助。我明天早上试试这个。另外,我在原帖中添加了更多信息。【参考方案2】:

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

【讨论】:

我将连接视频 - 但有没有办法让 AVAudioRecorderMPMoviePlayerController 协同工作? @Tim - 我从未使用过 AVAudioRecorder。将其分解为自己的问题可能是值得的。 感谢您的帮助。接受您的回答,因为我能够找到使音频也能正常工作的解决方案 - 因此我无法在录制时更改视频,所以我必须按照您提到的方式进行搜索。 另外,我很确定我只是单枪匹马给了你 Mortarboard 徽章:P(明天会出现) 哇。谢谢 :-)。我真的不了解堆栈溢出点系统,但它确实是获取/提供建议的好地方。

以上是关于在视频之间快速切换的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

录制视频时在前后摄像头之间切换

不要在 2 个视频之间显示桌面

Android 媒体播放器 - 如何在视频之间切换? [关闭]

在图像(缩略图)和视频之间切换

jQuery - 带有视频的标签...当我在标签之间切换时如何让视频停止播放?

在视频流之间切换