I'm writing an application where the user can record up to 6 video clips each with a duration of 2 seconds. When the video clips are recorded the user can play with them using 6 buttons - one for each clip. The user can then record a movie by switching between the 6 clips. The problem is that I need near instantaneous switching between the 6 clips when the user presses a button - otherwise the illusion of playing with the clips is lost - the functionality is somewhat similar to the app called CamBox in the App Store.
I first tried initializing every clip with and AVAsset in an AvPlayerItem in an AVPlayer every time the user pressed a button. The output of the player was directed at a an AVPlayerLayer in my main view. The problem is that the time it takes to load and start playing is quite long, meaning the the video lags when the user presses the buttons in rapid succession.
I the decided to try to preload all the clips using 5 AVPlayers and 5 AVPlayerLayers. The 5 PlayerLayers are inserted into my main view and when the user presses a button the currently playing AVPlayer is paused and rewound and the the currently visible AVPlayerLayer is hidden. The new AVPlayer is started and the corresponding AVPlayerLayer is shown. It works pretty ok being much faster than my first solution although not instantaneous but the problem is that I can only preload 4 clips meaning than when the user presses the button that play the last two the it lags big time. Below is my code to preload the clips
-(void)loadVideos
{
layers = [[NSMutableArray alloc] initWithCapacity:6];
players = [[NSMutableArray alloc] initWithCapacity:6];
for(int i = 1; i < 7; i++)
{
NSURL* fileURL = [NSURL fileURLWithPath:[self getFileName:i]];
AVPlayerItem* avPlayerItem = [[[AVPlayerItem alloc] initWithURL:fileURL] autorelease];
[avPlayerItem addObserver:self forKeyPath:#"status" options:0 context:nil];
AVPlayer *avPlayer = [[[AVPlayer alloc] initWithPlayerItem:avPlayerItem] autorelease];
[avPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
[avPlayer addObserver:self forKeyPath:#"currentItem" options:0 context:nil];
AVPlayerLayer* layer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
layer.frame = self.playerView.bounds;
[playerView.layer addSublayer:layer];
[layers addObject:layer];
[players addObject:avPlayer];
layer.hidden = YES;
}
}
The event handler for the 6 buttons looks like this:
- (IBAction)takeBtnClicked:(id)sender {
int tag = ((UIButton*)sender).tag;
AVPlayer* player;
AVPlayerLayer* layer;
if (layerIndex > -1) {
player = [players objectAtIndex:layerIndex];
layer = [layers objectAtIndex:layerIndex];
[player pause];
layer.hidden = YES;
[player seekToTime:kCMTimeZero];
}
layerIndex = tag-1;
player = [players objectAtIndex:layerIndex];
layer = [layers objectAtIndex:layerIndex];
[player play];
layer.hidden = NO;
}
I'm prette sure that the limitation of 4 preloaded video clips is a hardware limitation, but what is the alternative. Does anybody have any ideas?
Thanks in advance.
See my answer for iphone-smooth-transition-from-one-video-to-another, it shows a library you can use to implement this logic and an example app with 3 buttons that kick off animated clips. Each clip also has an associated sound effect.
Related
I created an app for streaming audio (and send mail as well) but I have a problem, my app will not keep playing after I press the home button.
I use XCODE 6.4 and the mediaplayer.framework to play the stream it is in viewcontroller and I have been activating the background mode Audio & airplay.
Can someone please let me know what I am doing wrong here, I really want to learn it.
code:
NSURL *videoStreamURL = [NSURL URLWithString:#"http://streamlink.m3u"];
_player = [[MPMoviePlayerController alloc] initWithContentURL:videoStreamURL];
_player.view.frame = CGRectMake(0, 530, self.view.frame.size.width, 38);
[self.view addSubview:_player.view];
[_player play];
I am trying to play a video clip that loops indefinitely. I am doing this the way Apple recommends; by setting up a notification that gets triggered by AVPlayerItemDidPlayToEndTimeNotification:
#property(nonatomic) AVPlayer *videoPlayer;
#property(nonatomic) AVPlayerItem *videoPlayerItem;
-(void)loadVideo
{
NSURL *url = [[NSBundle mainBundle] URLForResource:filename withExtension:extension];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
NSString *tracksKey = #"tracks";
[asset loadValuesAsynchronouslyForKeys:#[tracksKey] completionHandler:
^{
dispatch_async(dispatch_get_main_queue(),
^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:tracksKey error:&error];
if (status == AVKeyValueStatusLoaded)
{
[self setVideoPlayerItem:[AVPlayerItem playerItemWithAsset:asset]];
[videoPlayerItem addObserver:self forKeyPath:#"status" options:0 context:&ItemStatusContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector (videoPlayerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:videoPlayerItem];
[self setVideoPlayer:[AVPlayer playerWithPlayerItem:videoPlayerItem]];
[scenePlayerView setVideoPlayer:videoPlayer];
}
});
}];
}
When triggered, this calls my method to effectively rewind and play the clip again:
-(void)videoPlayerItemDidReachEnd:(NSNotification *)notification
{
[videoPlayerItem seekToTime:kCMTimeZero];
[videoPlayer play];
}
The problem is, there is a brief but visible pause in the video playback every time it hits this method and loops back to the beginning.
The video clips are H.264 and have been tested in other players to ensure that they have no visible "skips" in their content. This is all happening under iOS6 both in the Simulator and on an iPad2 and iPad3.
What am I doing wrong?
You have two possible approaches to get a really professional looking loop with no glitches (AVPlayer by itself will not work). First, you could take your original video and decode it to a series of RGB frames (images). Then encode a new h.264 video and feed the frames into a longer h.264 video that is much longer than the first. For example, you might encode a 10 second looping clips 6 times to make a clip that last one minute or 6 * 5 to make a clip that would play for 5 minutes without glitching when the loop restarts. Then, open the longer clip and play it with AVPlayer and it will not glitch for a period of time.
The second approach would be to use an existing library that already handles seamless looping. You can take a look at my library, see my answer to basically the same question iphone-smooth-transition-from-one-video-to-another. The character in the example at the linked answer uses a repeating "loop" of one video clip that loops over and over when no buttons have been pressed.
Unfortunately it appears this actually is not currently possible using AVPlayer as it stands- there is an unavoidable hiccup upon beginning playback in an AVPlayer in every case. So "messier" hacks appear to be the only way to solve this, using multiple AVPlayers, etc.
I managed to get it working with two AVPlayer, two AVPlayerLayer added to two different views (I've tried add to the same view and the upper one get stuck once in a while), and switch alpha value between these two views.
I have methods inside my open class controller that instantiates a moviePlayer, which is set to 'autoPlay = NO';
I have added the movieplayer.view as a subview of the controllers view, configured it and created a full screen button on top for starting the video. Since iOS4.3, this has been working fine. The button is transparent and the first frame of the video showed through ( which was a picture of a custom Automoble Auto-Start button).
Since iOS6, I only get a black screen.
Clicking the image-button does start the video as it should; calls [moviePlayer play]
Has something changed that I have not taken into consideration?
I have provided the two sections of code I think are necessary.
#define INTRO_MOVIE #"Intro.mov"
-(void)viewDidLoad
{
if(SHOULD_PLAY_INTRO_VIDEO)//Debug switch to ignore the intro video
{
// Prepare the movie and player
[self configureIntroMoviePlayer];
[self.view addSubview:moviePlayer.view];
[self.view bringSubviewToFront:moviePlayer.view];
// Add and Show the Start Button to start the App
[self configureStartButton];
[self.view addSubview:startButton];
}
}
-(void)configureIntroMoviePlayer
{
LOGINFO
// Prepare the Intro Video
NSString *pathToIntroVideo = [ mainFilePath_ stringByAppendingPathComponent: INTRO_MOVIE];
NSURL *URLToIntroVideo = [NSURL fileURLWithPath:pathToIntroVideo];
moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:URLToIntroVideo];
[moviePlayer setShouldAutoplay:NO];
moviePlayer.view.frame = CGRectMake(0, -20, 1024, 768);
[moviePlayer setControlStyle:MPMovieControlStyleNone];
//fixing video brightness Difference with iPad2
if(isIpad2)
{
moviePlayer.backgroundView.backgroundColor = [UIColor blackColor];
moviePlayer.view.alpha = .99;
}
// Create the sKip button for cancelling the Intro Video
skipIntro = [UIButton buttonWithType:UIButtonTypeCustom];
[skipIntro showsTouchWhenHighlighted];
skipIntro.frame = CGRectMake(900, 20, 111, 57);
[skipIntro addTarget:self action:#selector(skipIntroWasPressed) forControlEvents:UIControlEventTouchUpInside];
}
I am not sure why I got a -1 rating for this question for lack of research or clarity?
Maybe I do not know the proper usage of this forum.
I apologize.
I did find that adding [moviePlayer prepareToPlay] solved the problem. Like I said, it was odd that the first frame always showed up prior to iOS 6.
Have you tried:
[moviePlayer.view addSubView:startButton];
I embedded a MPMoviePlayerController on my mail view. I can play/pause the movie and seek forward/backward. But when I touch the "fullscreen button" the movie stops and the playback state is set to MPMoviePlaybackStateStopped... Should the movie be played in full screen?
Here is my code:
MPMoviePlayerController *player = [[MPMoviePlayerController alloc] initWithContentURL:videoUrl];
player.shouldAutoplay = NO;
player.movieSourceType = MPMovieSourceTypeFile;
player.controlStyle = MPMovieControlStyleEmbedded;
player.allowsAirPlay = YES;
player.view.frame = CGRectMake(xPos, yPos, width, height);
[self.view addSubview:player.view];
I found my bug: when pressing the full screen toggle button in MPMoviePlayerController's view, the method "viewWillLayoutSubviews" is invoked. I could never imagine this behavior...
I hope my experience can be useful to other developers.
Remember that any containing ViewController will have its viewWillDisappear, viewDidDisappear methods invoked when the MPMoviePlayerController goes full screen. Also, viewWillAppear and viewWillDisappear get called when it comes back from full screen.
If you have any logic in there that affects video playback behavior, it'll get called unless you use some conditional logic to see if the video is still playing.
I use a MPMoviePlayerController to play audio (ex: http://www.stanford.edu/group/edcorner/uploads/podcast/ballmer090506.mp3) or video (ex: http://video.ted.com/talk/podcast/2011U/None/RicElias_2011U.mp4)
myMoviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:theURL];
myMoviePlayer.view.frame = CGRectMake(-80, 80, 480, 320);
myMoviePlayer.controlStyle = MPMovieControlStyleFullscreen;
myMoviePlayer.movieSourceType = MPMovieSourceTypeFile;
myMoviePlayer.scalingMode = MPMovieScalingModeAspectFit;
myMoviePlayer.useApplicationAudioSession = TRUE;
[myMoviePlayer prepareToPlay];
[myMoviePlayer play];
It works well, my only one problem is during the loading time (between 5s and 20s if I have a bad 3G reception), I have a black screen.
I would like to have MPMoviePlayerController diplayed with the "Loading..." label and the UIActiviyIndicator in his top bar during this time, like YouTube videos.
How can I do that?
Thanks.
Just use MPMoviePlayerViewController instead of MPMoviePlayerController. It should do what you want.