Updating the progress bar in iOS 5 - iphone

I am developing an iOS audio player, and I want to implement a progress bar that will indicate the progress of the current song which is playing.
In my ViewController class, I have 2 double instances - time and duration, and a AVAudioPlayer instance called background.
- (IBAction)play:(id)sender {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"some_song" ofType:#"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
background = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
background.delegate = self;
[background setNumberOfLoops:1];
[background setVolume:0.5];
[background play];
time = 0;
duration = [background duration];
while(time < duration){
[progressBar setProgress: (double) time/duration animated:YES];
time += 1;
}
}
Could anyone explain what I am doing wrong?
Thanks in advance.

you don't update the progress of the progress bar during playback. When you start to play the sound you set the progress bar to 1, to 2, to 3, to 4, to 5... to 100%. All without leaving the current runloop. Which means you will only see the last step, a full progress bar.
You should use a NSTimer to update the progress bar. Something like this:
- (IBAction)play:(id)sender {
/* ... */
[self.player play];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.23 target:self selector:#selector(updateProgressBar:) userInfo:nil repeats:YES];
}
- (void)updateProgressBar:(NSTimer *)timer {
NSTimeInterval playTime = [self.player currentTime];
NSTimeInterval duration = [self.player duration];
float progress = playTime/duration;
[self.progressView setProgress:progress];
}
when you stop playing invalidate the timer.
[self.timer invalidate];
self.timer = nil;

Related

Saving NSTimer's time to disk on entering background and retrieving on foreground

I want my timer to work in the background, and I have figured the only way to actually do that is to save the time on appliationDidEnterBackground and retrieve it with applicationDidLaunchWithOptions. My timer uses Core Data and my timeInterval (testTask.timeInterval) is saved after every decrement as so:
-(IBAction)startTimer:(id)sender{
if (timer == nil) {
[startButton setTitle:#"Pause" forState:UIControlStateNormal];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerAction:) userInfo:nil repeats:YES];
} else {
[startButton setTitle:#"Resume" forState:UIControlStateNormal];
[timer invalidate];
timer = nil;
}
}
-(void)timerAction:(NSTimer *)t
{
if(testTask.timeInterval == 0)
{
if (self.timer)
{
[self timerExpired];
[self.timer invalidate];
self.timer = nil;
}
}
else
{
testTask.timeInterval--;
NSError *error;
if (![self.context save:&error]) {
NSLog(#"couldn't save: %#", [error localizedDescription]);
}
}
NSUInteger seconds = (NSUInteger)round(testTask.timeInterval);
NSString *string = [NSString stringWithFormat:#"%02u:%02u:%02u",
seconds / 3600, (seconds / 60) % 60, seconds % 60];
timerLabel.text = string;
NSLog(#"%f", testTask.timeInterval);
}
-(void)timerExpired{
UILocalNotification* localNotification = [[UILocalNotification alloc] init];
localNotification.alertBody = #"Time is up";
localNotification.alertAction = #"Ok";
localNotification.timeZone = [NSTimeZone defaultTimeZone];
[[UIApplication sharedApplication]presentLocalNotificationNow:localNotification];
}
These methods are in a detail view controller, which is initialized by this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
What I don't understand is...how would I access the timeInterval (each detailviewcontroller has a different timeinterval...) such that I can put it in appliationDidEnterBackground? Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
First, you shouldn't be using a direct countdown like this if you care at all about accuracy. The NSTimer will not fire on exact one-second intervals, and delays will accumulate. The better way to do this is to create an NSDate when the timer starts, and get the NSTimeInterval since then at each tick, then calculate the minutes and seconds.
For each view controller to store its own start time, you can register any object to receive the UIApplicationDidEnterBackgroundNotification. This is posted at the same time that the app delegate gets applicationDidEnterBackground:.
Also, I am guessing I should save the time that the application enters the background and then save the time when it enters the foreground, and subtract? and then I would subtract that value from the timeinterval, correct?
Yes:
NSTimeInterval idleTime = [dateReturnedToForeground timeIntervalSinceDate:dateEnteredBackground];
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startDate];
elapsedTime -= idleTime;
will give you the active time that has elapsed for your timer.

Playing a .mov file in UIImageView appears to lose frame rate?

I have a 480p MOV file that looks fantastic (quality wise) in a UiImageView. However, it appears to lose framerate when being played. Is there a better way to play this movie w/out the controls? It needs to play like an animation on top of a UI.
Thanks in advance!
Here's my code:
-(void)imagesExtractionThread {
NSDate * now = [[NSDate alloc] init];
startTime = [now timeIntervalSinceReferenceDate];
do{
[self performSelectorOnMainThread:#selector(setImageToAvatar) withObject:nil waitUntilDone:YES];
usleep(9000);
}while(!closing);}
-(void)setImageToAvatar{
NSDate * now = [[NSDate alloc] init];
NSTimeInterval curTime = [now timeIntervalSinceReferenceDate];
currentTime = curTime - startTime;
if(currentTime>player.playableDuration){
NSLog(#"Current time %lf",currentTime);
currentTime = 0.0;
startTime = curTime;
if(player.playableDuration >0){
closing = YES;
avatar.hidden = YES;
}
}
UIImage *singleFrameImage = [player thumbnailImageAtTime:currentTime
timeOption:1];
//singleFrameImage = [self changeGreenColorWithTransparent:singleFrameImage];
//
/*sourcePicture = [[GPUImagePicture alloc] initWithImage:singleFrameImage smoothlyScaleOutput:YES];
[sourcePicture addTarget:filter];
[sourcePicture processImage];
singleFrameImage = [filter imageFromCurrentlyProcessedOutput];*/
avatar.image = singleFrameImage;}
I start the play process with this code:
player = [[MPMoviePlayerController alloc] initWithContentURL: url];
player.shouldAutoplay = NO;
[NSThread detachNewThreadSelector:#selector(imagesExtractionThread) toTarget:self withObject:nil];
Use AVFoundation framework and AVPlayer class to play the video file. You get free hand to customize the appearance, controls etc. You may hide the controls if you don't want them.

UIProgressBar for AvAudioPlayer playing progress

In my app playing audiofile for that i want to show UIProgressBar on the UIToolbar for audiofile playing progress. Anyone can help me how to code for it.
I use a timer to check the current duration, and then update my progress bar every half second, like so:
tmrCounter = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(updateElapsedTime) userInfo:nil repeats:YES];
-(void)updateElapsedTime{
if (player) {
[prgIndicator setProgress:[player currentTime] / [player duration]];
[lblTime setText:[NSString stringWithFormat:#"%#", [self formatTime:[player currentTime]]]];
}
}
-(NSString*)formatTime:(float)time{
int minutes = time / 60;
int seconds = (int)time % 60;
return [NSString stringWithFormat:#"%#%d:%#%d", minutes / 10 ? [NSString stringWithFormat:#"%d", minutes / 10] : #"", minutes % 10, [NSString stringWithFormat:#"%d", seconds / 10], seconds % 10];
}
Hope this helps!

while loop memory problem

The problem with this code is that when the while loop is executing, the memory usage is increasing continuously. I want to know why does this code keeps on increasing memory when its in while loop.
Which object here is eating the memory. What can i do so that the memory does not increase in the loop.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSNumber *totalTime = [[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration];
while (self.currentPlaybackTime < [totalTime floatValue])
{
NSNumber *currentTime = [[NSNumber alloc] initWithFloat:self.currentPlaybackTime];
if([[NSThread currentThread] isCancelled])
{
[NSThread exit];
[newThread release];
}
else if([totalTime intValue] - [currentTime intValue] == 1.0)
{
if(currentTime)
[currentTime release];
break;
}
[currentTime release];
}
[pool release]
That is some inefficient code you have there.... Heres a replacement that doesn't go mental allocating memory for no reason, further more you might want to put a sleep in there so it doesn't eat CPU
// not needed any more
//NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int totalTime = [[[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] intValue];
while (self.currentPlaybackTime < totalTime)
{
//not needed any more
//NSNumber *currentTime = [[NSNumber alloc] initWithFloat:self.currentPlaybackTime];
if([[NSThread currentThread] isCancelled])
{
[NSThread exit];
[newThread release];
}
else if((totalTime - self.currentPlaybackTime) <= 1.0) // you may never hit 1
{
break;
}
}
//[pool release]
I have solved this problem. Replaced the while loop with Timer and made few changes.
Created a timer which fires every second
timer = [NSTimer timerWithTimeInterval:1
target:self
selector:#selector(performAction)
userInfo:nil
repeats:YES];
then in performAction, check for the current playback time and invalidate the timer it the difference in time is <= 1 sec
int totalTime = [[[self nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] intValue];
if((totalTime - self.currentPlaybackTime) <= 1.0)
{
if(timer)
[timer invalidate];
/* Performed my desired action here.... */
}

how to pause and resume NSTimer in iphone

hello
I am developing small gameApp.
I need to pause the timer,when user goes to another view [say settings view].
when user comes back to that view , I need to resume the timer.
can anybody solve this issue ...
Thanks in Advance...
NSTimer does not give you the ability to pause it. However, with a few simple variables, you can create the effect yourself:
NSTimer *timer;
double timerInterval = 10.0;
double timerElapsed = 0.0;
NSDate *timerStarted;
-(void) startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:(timerInterval - timerElapsed) target:self selector:#selector(fired) userInfo:nil repeats:NO];
timerStarted = [NSDate date];
}
-(void) fired {
[timer invalidate];
timer = nil;
timerElapsed = 0.0;
[self startTimer];
// react to timer event here
}
-(void) pauseTimer {
[timer invalidate];
timer = nil;
timerElapsed = [[NSDate date] timeIntervalSinceDate:timerStarted];
}
This has been working out quite well for me.
You can't pause a timer. However, when the user goes to the settings view, you can save the fireDate of the timer and also the current date. After this you invalidate the timer and let the user do his/her stuff.
Once he/she switches back to the game, you create a new timer object and set the fire date to the old fire date plus the time the user was in the menu (oldTime + currentTime).
you can use this code to implement pause and resume functionality in NSTimer
//=========new timer update method=============
-(void) updateTimer {
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString = [dateFormatter stringFromDate:timerDate];
lblMessage.text = timeString;
pauseTimeInterval = timeInterval;
}
-(IBAction) startBtnPressed:(id)sender
{
//=============================new update with start pause==================
if(running == NO) {
running = YES;
startDate = [NSDate date] ;
startDate = [[startDate dateByAddingTimeInterval:((-1)*(pauseTimeInterval))] retain];
stopWatchTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self selector:#selector(updateTimer) userInfo:nil repeats:YES];
}
else {
running = NO;
[stopWatchTimer invalidate];
stopWatchTimer = nil;
[self updateTimer];
}
}
declare in .h file NSDate startDate;
NSTimeInterval pauseTimeinterval;
and in viewdidload pausetimeInterval=0.0; good luck
You can't pause an NSTimer. You can, however invalidate it and create a new one when needed.
on Start -
startNewCapture = [NSDate date];
on Pause -
captureSessionPauseDate = [NSDate date];
captureSessionTimeInterval = [captureSessionPauseDate timeIntervalSinceDate:startNewCapture];
on Resume -
NSDate *dateNow = [NSDate date];
startNewCapture = [NSDate dateWithTimeInterval:-captureSessionTimeInterval sinceDate:dateNow];
- (IBAction)pauseResumeTimer:(id)sender {
if (timerRunning == NO) {
timerRunning = YES;
[pauseTimerBtn setTitle:#"Resume" forState:UIControlStateNormal];
NSString *stringVal = [NSString stringWithFormat:#"%#",timeTxt.text];
stringVal = [stringVal stringByReplacingOccurrencesOfString:#":" withString:#"."];
float tempFloatVal = [stringVal floatValue];
int minuteValue = floorf(tempFloatVal);
float tempSecVal = [stringVal floatValue] - floorf(tempFloatVal);
int secondVal = tempSecVal*100;
minuteValue = minuteValue*60;
oldTimeValue = minuteValue + secondVal;
[timer invalidate];
timer = nil;
}
else
{
timerRunning = NO;
[pauseTimerBtn setTitle:#"Pause" forState:UIControlStateNormal];
startDate = [NSDate date];
timer = [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:#selector(timer:) userInfo:nil repeats:YES];
}
}
- (void)runTimer:(NSTimer *)timer {
NSInteger secondsAtStart = (NSInteger)[[NSDate date] timeIntervalSinceDate:startDate];
secondsAtStart = secondsAtStart + oldTimeValue;
NSInteger seconds = secondsAtStart % 60;
NSInteger minutes = (secondsAtStart / 60) % 60;
NSInteger hours = secondsAtStart / (60 * 60);
NSString *result = nil;
result = [NSString stringWithFormat:#"%02ld:%02ld",(long)minutes,(long)seconds];
timeTxt.text = result;
}
Did you end up figuring it out? I saw that you said that you cannot invalidate your timer when you go into another view. Can you explain what you mean by that? NSTimers cannot be paused, and methods to simulate pausing them usually involve invalidating them. You can then simulate "unpausing" by creating a new timer that will then start up again. This will simulate a timer being paused and unpaused.
For people who would like a potentially more convenient method, I wrote a controller class that can conveniently handle pausing and unpausing timers. You can find it here: https://github.com/LianaChu/LCPausableTimer
You can use the controller class that I wrote to create new timers, and then you can pause and unpause the timers by call the methods "pauseTimer" and "unpauseTimer" on the controller class.
I would greatly appreciate any comments or feedback like how you used it, what changes you would like to see, or any features you would like me to add. Please don't hesitate to reply with your comments here, or post on the issues tab on the Github page.