The right way to quickly switch between videos

I am creating an application that displays a specific video based on an external event, which may require the video being played to change quickly - once per second or more. However, there should be no spaces or delays between videos.

What is the best way to do this? There are only four videos, each of which is about two megabytes.

I considered creating four MPMoviePlayerControllers , and their views are added to the main view, but hidden and switched by pausing and hiding the current video, and then showing and playing the next video. Is there a more elegant solution?

Edit

Here is some more info for my exact sitaution:

  • Different video frames share mostly common pixels - so this is normal when a frame should be inserted during switching, but NOT suitable for black frames.
  • Each video lasts only about ten seconds, and there are only four videos. General state transitions are 1 ↔ 2 ↔ 3 ↔ 4-> 1.
  • Video playback should be compatible with simultaneous recording of AVAudioRecorder. As far as I can tell, MPMoviePlayerController not.
+8
ios iphone mpmovieplayercontroller avaudioplayer avaudiorecorder
source share
2 answers

MPMoviePlayerController is a singleton. Four copies will have the same pointer, the same look, etc. With a native player, I think you have only two alternatives: you need to change the contentURL property when you want a transition. If latency is thus unacceptable, another alternative is to create a longer video with shorter concatenated clips. You can create very fast jumps within one longer clip by setting currentPlaybackTime.

+2
source share

You need to tune and buffer all the video streams to avoid hiccups, so I don't think your MPMoviePlayerController multiple solution is too far from the mark.

However, this particular solution is potentially problematic, because each player has its own interface. User interfaces are not synchronized with each other, so you can display the control panel, and the other does not; one may be in full screen, etc. Switching between them will cause visual breaks.

Since this sounds like your video switch is not necessarily user initiated, I assume that you are not interested in the standard user interface of the video player.

I would suggest abandoning the base layer, AV Foundation. Theoretically, you can simply create an AVPlayerItem for each video. This is a flow control object with no user interface overhead, so it is perfect for what you are doing. Then you could, theoretically, create one AVPlayer and one AVPlayerLayer to handle the display. If you want to switch from one AVPlayerItem stream to another, you can call the AVPlayer message replaceCurrentItemWithPlayerItem: to exchange data with the data stream.

I did a small test project (GitHub) to test this, and, unfortunately, the direct solution is not entirely perfect. There is no video stream failure, but when switching from AVPlayer to AVPlayer, the presentation layer seems to briefly launch the previous frame of the previous movie in the size corresponding to the next movie. This seems to help highlight individual AVPlayer objects for each movie and switch between them on the player’s permanent layer. It seems to be an instant flash of the background, but at least it's a subtle defect. Here's the gist of the code:

 @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 

Half of the code is just to monitor the status of the players and ensure that playback does not start until all the videos have been buffered.

Highlighting three separate AVPlayerLayers (in addition to three AVPlayers) prevents any flash. Unfortunately, AVPlayer connected to the non-displayed AVPlayerLayer seems to suggest that it does not need to support the video buffer. Each switch between layers creates an intermittent video stutter. So nothing good.

A few things to consider when using AV Foundation:

1) The AVPlayer object does not have built-in support for looping. You will have to watch the end of the current video and manually search for zero time.

2) There is no interface in everything except the video frame, but again, I assume that this may be an advantage for you.

+8
source share

All Articles