I want to make part of video slow down while rest of it are normal speed, just like the slow mode video taken by iOS camera. How to do that? I've search AVFoundation but found nothing.
Thanks!
If you are only talking about creating this effect during playback (and not exporting it), you should be able to do so by just changing the rate property of AVPlayer at specific times. Use addBoundaryTimeObserverForTimes:queue:usingBlock: to get notified when it's time to change the rate.
CMTime interval = CMTimeMake(10, 1);
NSArray *times = #[[NSValue valueWithCMTime:interval]];
_boundaryTimeObserver = [_avPlayer addBoundaryTimeObserverForTimes:times
queue:nil
usingBlock:^{
[_weakPlayer setRate:0.5];
}];
The rate property works as follows:
rate = 0.0; // Stopped
rate = 0.5; // Half speed
rate = 1.0; // Normal speed
For slow motion playback, the AVPlayer property canPlaySlowForward must be set to true.
Remember to remove the time observer when you're finished with it, and make sure to use an unretained reference to self or to the player within the block in order to avoid retain cycles.
Related
Since we are talking about programmatically, Instruments are not under my consideration.
Some reference listed in advance:
Calculate fps (frames per second) for iphone app
Display FPS on iOS onscreen (without Instruments)
At what framerate does the iOS UI run animations at?
1. Using CADisplayLink
According to the doc,
The duration property provides the amount of time between frames. You
can use this value in your application to calculate the frame rate of
the display...
So in my demo project, I add a displayLink to mainRunLoop for UITrackingRunLoopMode:
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(screenDidUpdated)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
and use duration to calculate FPS. But out of my expectation, the duration is always 0.016667 (~ FPS 60) not matter the tableView scrolls smoothly or not.
How I make the tableView lag is to add one line in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath:
NSData *data = [NSData dataWithContentsOfURL:url];
Then I turned to displayLink.timestamp, and it worked.
2. Observing drawRect:?
My second idea is to observe drawRect:, I thought when the tableView scrolls, its drawRect: would be called frame by frame, then I could calculate the FPS according to the time diff between drawRect: callings.
But it failed, cause the drawRect: was called only once (while cells for many times). This way is similar to Using CADisplayLink, but maybe I chose the wrong position/method(like drawRect:) to observe, any recommended method for me to observe?
Questions:
How the Instruments measure the FPS accurately?
Is the doc said using duration to calculate FPS wrong?
What's the right/best method to observe to calculate the FPS?
Thanks!
As the documentation for CADisplayLink states, duration is basically just 1 / maximumFramesPerSecond. You need to check the discrepancy between targetTimestamp and timestamp to detect dropped frames:
The duration property provides the amount of time between frames at the maximumFramesPerSecond. To calculate the actual frame duration, use targetTimestamp - timestamp.
This works but requires some setup.
Set up a CADisplayLink and leave the frameInterval as the default (shoots once per frame)
This CADisplayLink needs to actually be added to a runLoop and fire at least once to get to #3
Once that calls back, or inside the callback, you can get the frame rate. Here's some Swift code to give you the idea, but where displayLink is your CADisplayLink instance
var framesPerSecond : Int {
var retVal : Int = 60 // default value, just in case link.duration is still 0
if let link = displayLink where link.duration > 0 {
retVal = Int(round(1000 / link.duration)/1000)
}
return retVal;
}
Here's my code:
-(void)play {
animation = [CAKeyframeAnimation animationWithKeyPath: #"contents"];
for (UIImage *image in folder.images) {
[images addObject:(id)image.CGImage];
}
animationArray = [images subarrayWithRange:NSMakeRange(selected, selectedEnd - selected)];
timeBetweenAnimation = 1.0/framesPerSecond;
totalAnimationTime = folder.images.count * timeBetweenAnimation;
animation.duration = totalAnimationTime;
animation.values = animationArray;
animation.delegate = self;
beforeAdd = CACurrentMediaTime();
[displayImage.layer addAnimation:animation forKey:#"contents"];
afterAdd = CACurrentMediaTime();
}
-(void)animationDidStart {
afterStart = CACurrentMediaTime();
}
I am using Core Animation to animate an array of images and I want to get the current image in the animation when I pause the image. I realize that a method of doing this would be to get the image from "animationArray" using timing ratios (played time / total duration time), but I can't capture the begin time. I've tried capturing the begin time, using CACurrentMediaTime(), before and after the call to addAnimation:forKey: and also after animation did start, but it's innaccurate (especially when I have a large array of images). With a larger array of images, the "beforeAdd" and "afterAdd" times are at least a second off. Also, "afterStart" is anywhere between .04 and .08 seconds off independent of the size of my images array.
In the worst case scenario, I could use the "afterStart" begin time. However, if I'm trying to animate 100 images in 1 second, getting the image using this method would be inaccurate by 4 frames. Therefore, I either need a method that doesn't use timing in order to get the current frame of the animation, or I need a way to get a more accurate begin time. Is this possible?
NOTE: I don't want to use UIImageView animation to do this because that animation technique doesn't have smooth transitioning between frames. If there were a way to have smooth transitioning between frames using UIImageView animation I would reconsider, although I'm pretty sure there will still be an issue when calculating the begin time.
in my app i want to add feature . swipe on video take the video to next 2 sec from current playback time. but my player not doing this accurate and exact according to time which i pass to function it jump the current play back time at any where else. i already search a lot and found may be this is due to key-frame of my video i think i need to increase key-frame of video if yes
then (1) what is the best way to increase key-frame of video?
if this issue can solve without increasing key-frame then
(2) how can i do this ?
here is my code
-(void) handleOneFingerSwipeRight
{
if(labelTimer.isValid)
[labelTimer invalidate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(signleTap) object:nil];
[videoPlayer pause];
double d = 0;
d = floor([videoPlayer currentPlaybackTime]);
d = d+2.0;
NSLog(#"current time %f",floor([videoPlayer currentPlaybackTime]));
NSLog(#"new time %f",d);
[videoPlayer setCurrentPlaybackTime:d];
[videoPlayer play];
[self setLable:[NSString stringWithFormat:#"Adaptive Forward %f",d]];
}
Can't change currentPlaybackRate... Any value < 0 make playing little slower than normal speed. I want to change playing speed from 1 to 1/2...1/4 and 1/8. But any changes not working. The Player continues playing with own speed(little slower or normal. Even if I set 0.00001 for currentPlaybackRate the playing speed not change). So, How to make slow motion effect on MPMoviePlayerController.
// Little slower than normal, but same for any value
moviePlayerController.currentPlaybackRate = 0.5;
moviePlayerController.currentPlaybackRate = 0.25;
moviePlayerController.currentPlaybackRate = 0.75;
moviePlayerController.currentPlaybackRate = 0.125;
// Normal
moviePlayerController.currentPlaybackRate = 1;
MPMoviePlayerController is deprecated in iOS 9. You should use AVPlayerViewController instead and use avplayer.Rate property to set the playback rate of the current video.
I use use AVPlayer to implement a custom player.
Some video playerItem provide the current time and wrong duration. After to seek time use slide to seek time many times. Call the API
When I seek to zero, some video can not be precisely seeked.
[self.player seekToTime:CMTimeMakeWithSeconds(time, NSEC_PER_SEC)
toleranceBefore:CMTimeMake(1, 1)
toleranceAfter:CMTimeMake(1, 1)
completionHandler:^(BOOL finished) {
if (finished) {
IVCLogV(#"seek finish!");
}
else
{
IVCLogV(#"seek interrupted");
}
if (completionHandle) {
completionHandle(finished);
}
}];
I change the codes according the mediaTime.timeScale. Now I discover the video stream have changed the video duration and current time after several play.
Make sure that the Timescale of the media being played matches with your timescale. I can see you have used NSEC_PER_SEC as timescale. You may have to scale your CMTime input to seelTo method.
CMTime timeAccordingToMediaTimescale = CMTimeConvertScale(time, mediaTime.timescale, CMTimeRoundingMethod);