views:

147

answers:

3

Hey

I'm using AVAudioPlayer to play music in my iPhone app.

In a class that I wrote I have an array that contains random ascending integers. (2, 4, 9, 17, 18, 20,...) These integers represent times in the song at which a certain event should occur. So if you take the above array, after 2 seconds of the song playing, some method should be called. After 4 seconds, another method should be called. And so on.

I have tried using a repeating NSTimer:

NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerTick) userInfo:nil repeats:YES];

Everytime it fires, it checks whether the value of the Audioplayer and of the current arrayindex are the same:

- (void) timerTick {
   if([[myArray objectAtIndex:currentIndex] intValue] == (int)(player.currentTime)) {
   //here the event-method is called
   currentIndex++;
   }
}

This code actually works, but only for some time. After some time however, myTimer and the timer that controls the musicplayer are out of sync. So it misses an element of myArray and an infinite loop starts. I don't know exactly why they get out of sync, but I think it could be because the timer and the player aren't being started at exactly the same time or maybe because of short performance lags.

I think I have to approach this in a totally different way. Is key-value observing a way to do this? I could add my class as an observer to the player object, so that it gets notified when the player.currentTime value changes. But that would cause a LOT of notifications to be send and I think it would be really bad for performance.

Any help much appreciated!

A: 

It could be that the timers are reasonably in sync, but your code just takes to long to execute (i.e: longer then 1 second).

Couldn't you just use the timer of the musicplayer, and spawn a thread each time an event should occur? This way the timer stays uninterrupted, and your thread will do what it needs to do (lets say the heavy stuff).

If you reall y need two timers, I guess you could create a background threads that keeps those two timers in sync, but I think you're asking for trouble there..

MiRAGe
"Couldn't you just use the timer of the musicplayer" I would love to! I actually don't need another timer. But how should I do this? How can I tell if it's time to call my event-method? When should I check if([[myArray objectAtIndex:currentIndex] intValue] == (int)(player.currentTime))?
megamer
A: 

Real world synchronization with music is very hard because users can notice mis-syncs of just a tenth of a second or less. You might find that AVAudioPlayer is to simple for what you need. You might have to control the rate the music plays using AudioQueueServices so that you can sync the music to the code instead of the other way around. You could see time to fire your code coming up and then start the methods before the music actually plays. Done skillfully this would sync the music most of the time.

TechZen
+1  A: 

Ok here is my solution: I found an open source app that does almost the same thing my app should do, which helped me a lot. I'm going to stick with the code I already have, with a little modification it should be precise enough for my purposes. Here it is:

currentIndex = 0;
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(timerTick) userInfo:nil repeats:YES];


- (void) timerTick {
 if(timerRunning){


  if([[myArray objectAtIndex:currentIndex] intValue] <= (int)(player.currentTime*100)) { 
   //some event code
   currentIndex++;
  }
 }
}

The important change is from == to <= in the if-condition. When it gets asynchronous and misses an element of myArray, the error is corrected the next hundredth of a second. That's good enough.

Thanks for your help!

megamer