I'm creating an app where the user is supposed to video record himself while another video is playing on the screen next to the camera view. The result will be two video files, one source, and one recording. Using AVCaptureSession etc., I have successfully managed to record a video at the same time as another video is playing on the screen. The problem is that It's not completely in sync.
This is how I have set it up right now:
-(void)playAndRecordInSync //Session is already set etc.
{
player = [AVPlayer playerWithURL:url];
playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[playerLayer setFrame:leftCameraView.bounds];
[playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[leftCameraView.layer addSublayer:playerLayer];
//Will stop recording camera when source video reaches end(notification):
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[player currentItem]];
//Start playback:
[player play];
//Start recording:
[movieOutput startRecordingToOutputFileURL:outputURL recordingDelegate:self];
}
- (void)playerItemDidReachEnd:(NSNotification *)notification {
[movieOutput stopRecording];
}
The result of this, is two videos, but the second (recorded) video is slightly longer than the source/original video. When playing them on top of each other (at the same time), then video nr. 2 has a delay of almost half a second. It feels like video2 has started recording way too soon, because of the "extra" time. It could probably also be that the playback started too late. Either way, I don't know how to make any consistency here based on this. I could probably play around a lot with sleep etc, but that would only work for my phone, as this has something to do with how fast the phone is.
Even if there is a delegate method like -(void)willStartPlaying for the AVPlayer, which I don't think there is, there would still be a problem with sync, as there's a minor wait till the return. Is there a way to fire two commands at once? Or is there another solution for this?
EDIT
I've been reading this (AVSynchronizedLayer), but I'm not sure if this is applicable to my situation, and I don't understand how to proceed.
I found out the delay was always caused by the playback starting too late, but both the recording and the playback were stopped at the same time, so I resolved this by finding v2.duration - v1.duration, and subtract the result from the beginning of v2, using AVAssetExportSession. When playing back both videos at once now, in different layers, there's still some delay from starting both, but by exporting the videos, I've confirmed that they're the same length and in sync now.
Related
So I'm using AVPlayerViewController and what i'm doing to load the video is basically this:
I have a large number of videos in my database, I make a call to the db to retrieve the next video after the current video has finished playing, the issue I think may have to do with the buffer being full? After looking around on stack & the internet I couldn't find an answer that helped me.
I'm trying to find out how can I reset the buffer after each video is played, or atleast know when the buffer is out of space so that I can display an image or something while the buffer loads, so then I can make a call to continue displaying videos. Below is my code for the AVPlayerViewController.
AVPlayerViewController *playerViewController = [[AVPlayerViewController
alloc] init];
playerViewController.player = [AVPlayer #"www.videoURL.com"];
self.avPlayerViewcontroller = playerViewController;
[playerViewController.view setFrame: userView.bounds];
[userView addSubview:playerViewController.view];
self.avPlayerViewcontroller.player.volume = 0.0;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(volumeChanged:)
name:#"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
[playerViewController.player play];
[userImageView setHidden:NO];
Edit:1) I'm about to play about 15-25 videos before the video play gives me this:
Edit:2) But if I close the app and then re-open it, the videos play on the app like nothings wrong. I'm not really sure whats going on.
For anyone that encounters this same scenario, The issue for me was inside of the the same block that I was making my API call I was initializing this:
AVPlayerViewController *playerViewController =
[[AVPlayerViewController alloc] init];
AVPlayerViewController was (STRONG) reference causing a retain cycle within the block, so each time I made an API call to grab a new video I was init a whole new AVPlayerViewController without releasing the previous. Along with that, I found that I had other objects within the API success response were being referenced strong as well. So I went back made those a WEAK reference and then I moved
AVPlayerViewController *playerViewController =
[[AVPlayerViewController alloc] init];
To the view did load method, so it's initialized ONCE instead of everytime a new video URL comes through.
It took me all day to figure out what was going one, because after looking at other users having problems with this, I was so focused on "BUFFERING" or running out of buffering was the culprit, but comes to find out it was just bad implementation of objects and misplacements.
Good luck to future programmers.
I have a relatively simple setup involving 1 AVPlayer looping some ambient audio in the background and a second player playing a shorter sound at certain points.
What I've observed is that when the short sound is played, I hear the ambient clip cut out for about a second while there is a staticy pop. It then proceeds to continue playing while the short sound is played at the same time. This only happens on device - it's not noticeable on the sim, which seems to point to a potential performance issue.
I can't quite figure out why the first AVPlayer has this blip. Here is the code for the ambient player:
NSString *path = [[NSBundle mainBundle] pathForResource:kAmbientTrack ofType:#"mp3"];
_ambientPlayer = [AVPlayer playerWithURL:[NSURL fileURLWithPath:path]];
[self.ambientPlayer play];
It's slightly more complex, as I also listen for a notification when it ends and the restart it, but this issue happens even during the initial play before any looping occurs.
So while that is going in the background, I play another clip like so:
self.announcementPlayer = [[AVPlayer alloc] initWithURL:[myObject audioPathUrl]];
[self.announcementPlayer play];
So nothing too unique there - just two AVPlayers playing.
The only other piece of interest is how I set up the audio session when the app launches.
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
I'm doing this to allow the user to play music from other apps in the background.
I think my code is pretty straight forward, and really have no idea why I would be getting this blip.
I fixed this somewhat by changing the audio players to be AVAudioPlayers. That resolved the popping in most cases. However, I still get the ducking. Specifically, if I play an AVAudioPlayer and, while it's still playing, I start to load an AVPlayer, it cuts off the AVAudioPlayer for about a second. Here's what I'm talking about:
[myAVAudioPlayer play];
AVPlayer *player = [AVPlayer playerWithURL:[self.currentExercise videoPathUrl]];
The addition of that second line causes the first second or so of myAVAudioPlayer to be silent.
I'm trying to play a streaming video with a AVPlayer without any network delay. Fortunately, our app has a progress screen before the screen that plays the video. I'm hoping to use some of the time on this progress screen to pre-load the video so it plays without delay on the next screen.
The most promising approach that I've come up with is to use an AVQueuePlayer to play 2 videos, the first would be a video that I play off screen (so you don't see it) and is silent, so you don't hear it. From what I've read on SO AVQueuePlayer buffers the nth+1 video when the nth video is near completion.
Here's my code to do this:
NSString *blackVideoPath = [[NSBundle mainBundle] pathForResource:#"black10-counting" ofType:#"MOV"];
AVPlayerItem *blackVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:blackVideoPath]];
AVPlayerItem *realVideoItem = [AVPlayerItem playerItemWithURL:_videoWithEffectsURL];
NSArray *theItems = [NSArray arrayWithObjects:blackVideoItem, realVideoItem, nil];
AVQueuePlayer *theQueuePlayer = [AVQueuePlayer queuePlayerWithItems:theItems];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[theItems firstObject]];
AVPlayerLayer* playerLayer = [AVPlayerLayer playerLayerWithPlayer:theQueuePlayer];
playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:playerLayer];
[theQueuePlayer play];
This does play my first video (for debugging I have a video with a counting soundtrack) but when reach the end and advance to the next screen (in playerItemDidReachEnd) my real video doesn't play immediately.
Just to make sure my URLs were correct I reversed blackVideoItem and realVideoItem in the list and I do hear the soundtrack to my "real" video.
I've searched SO a lot and it doesn't seem like there is a way to play a streaming video without a delay. I'd love to be wrong.
You may be able to do this without AVQueuePlayer.
As early as possible, create the AVPlayer with the remote asset.
Observe the AVPlayerItem (playback buffers, etc.) to make sure it has buffered enough data to play smoothly at the beginning. (See: Preloading video to play without delay)
Show your progress screen.
When the player item is ready, remove the progress screen
Depending on the network connection, you may not experience smooth playback the whole way through, but monitoring the player item should let you show some UI if your player is buffering.
I've created an AVPlayer and set the forwardPlaybackEndTime to make a local video stop at a given timestamp. Sure enough, the video stops at the time I've requested. All good.
Now I want the video to continue when triggered by a user action (touching a button, for example). Unfortunately, I can't seem to make that happen without the video restarting from the beginning.
I'll spare you all of the AVPlayer setup code (which is mostly taken from the AV Foundation Programming Guide), but given these variables:
AVPlayer *avPlayer;
AVPlayerItem *playerItem;
I can set the end time like so:
[playerItem setForwardPlaybackEndTime: CMTimeMake(30, 30)];
To attempt the resume, I've tried this:
[playerItem setForwardPlaybackEndTime: CMTimeMake(30, 30)];
[avPlayer setRate: 1.0];
No dice. I've also tried setting the end time and calling play. No luck. I've tried seekToTime to put the playhead at the place where the video stopped in case that would help. It doesn't.
Can someone please explain how to make this work? Thanks!
Try setting the forwardPlaybackEndTime back to the default value, kCMTimeInvalid then continue to play the video.
[playerItem setForwardPlaybackEndTime: kCMTimeInvalid];
[playerItem seekToTime: CMTimeMake(30, 30) toleranceBefore: kCMTimeZero toleranceAfter: kCMTimeZero];
[avPlayer play];
I am trying to have MPMoviePlayerController reach a programmed endPlaybackTime, then reassign the initial, current, and end times and "resume" play
So first play, say, from the start to 4 seconds, stop/pause, then resume and play from 4 to 8, etc...
but after I reassign current, initial, and endPlaybackTimes and run [mplayer play], the video restarts from the originally times (start to 4s) and plays to the original end time, even though debug messages confirm the new times after the second play
NSURL *fileURL = [NSURL fileURLWithPath:filepath];
_mplayer3 = [[MPMoviePlayerController alloc] initWithContentURL:fileURL];
_mplayer3.controlStyle = MPMovieControlStyleNone;
[_mplayer3.view setFrame: self.view.bounds];
[self.view insertSubview:_mplayer3.view belowSubview:_TopBrag];
_mplayer3.endPlaybackTime = 4.0;
[_mplayer3 setShouldAutoplay:NO];
[_mplayer3 prepareToPlay];
[_mplayer3 view].userInteractionEnabled=YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(parallaxDownDidFinish)
name:MPMoviePlayerPlaybackDidFinishNotification
object:_mplayer3];
[_mplayer3 play];
on the end notification, there's a gesture recognizer is created that appears to work correctly. in the gesture recognizer:
_mplayer3.initialPlaybackTime=_mplayer3.endPlaybackTime;
_mplayer3.currentPlaybackTime=_mplayer3.endPlaybackTime;
_mplayer3.endPlaybackTime+=4.0;
if (_mplayer3.endPlaybackTime > _mplayer3.duration)
_mplayer3.endPlaybackTime = _mplayer3.duration;
[_mplayer3 play]
and the video plays from 0 to 4 instead of 4 to 8, even though NSLogs after the play suggest the times are what's desired
appreciate any help
MPMoviePlayerController does not adhere to the initialPlaybackTime if not used on a fresh instance. You will need to release and realloc/assign the player to get this working. All you need to do is to reuse the initial code shown in your question once the player aught to continue.
Update:
Since you seem to be keen on keeping the player view active and as you are not using the standard user interface, I would suggest you to use AVPlayer instead. It is much more flexible and for your job it seems to be the right choice then.
I'm not sure whats the problem, because it should work, the problem i think is that you are in IOS 6, IOS 6 doesn't let you make such a small change, but if in your swipe you change the initial playtime to 9 or higher it should work....
try it and then let me know ;)