12

When selecting a row the following code sets up a set of AVAudioPlayers to playback at a certain date (in this case, 50 players playing in the interval of 1 second). Since I want the whole process to restart when touching again I need to break the setup in the for loop since it takes a few seconds to setup the players.

Apart from that, each player is being removed after finishing playback using the audioDidFinishPlaying delegate method of AVAudioPlayerDelegate. I did not include this in the code since it is not relevant to the question.

I've tried using a flag inside the for loop to check whether setup is allowed but that doesn't work.

var players: [AVAudioPlayer] = []
var loadError: NSError?

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

  // Removes the players that have been setup already.
  players.removeAll()

  // The for loop from the previous row selection should be stopped here.
  for i in 0..<50 {
    do {
      let player = try AVAudioPlayer(contentsOfURL: soundUrls[i])
      players += [player]

      // The process of setting these up takes a few seconds, I need to break it.
      print("Firing timer")   
      player.playAtTime(player.deviceCurrentTime + NSTimeInterval(i))

    } catch let error as NSError {
      loadError = error
    }
  }
}

What happens is, that the setup triggered by the previous row selection will continue until it is finished and only then the new for loop starts. I need to break it earlier.

I can't figure out how to tackle this. Maybe by removing the processes from the main thread(How?)? Any help much appreciated!

nontomatic
  • 2,003
  • 2
  • 24
  • 38
  • your loop will crash if soundUrls has 50 items since 0...50 runs 51 times. – ergoon Oct 12 '16 at 18:59
  • Thanks for the correction, this was just my mistake when making up this example code here in the text field, I've corrected it. Still looking for an answer! – nontomatic Oct 12 '16 at 21:35

1 Answers1

1

I'm a little bit confused about your statement of the problem, but I'll try to give a suggestion anyway. You say that you set up the players when selecting a row, but the code to set them up is in cellForRowAtIndexPath. So it's set up and playing starts when a cell is returned and displayed in your table view.

What exactly are you trying to achieve? You have a table view with a number of rows, and whenever you tap on a cell the fifty sounds have to start playing one after the other (1 second apart). If you tap the same cell again they should stop and restart, is that it?

Then what I would do is set up the 50 players in viewDidLoad of your TableViewController. Use prepareToPlay(). Start them when needed.

Then if you need to restart them, just cycle through them, check if they are still playing using isPlaying. Pause them if needed, set the current time to 0 and call playAtTime again. Don't remove the players in audioDidFinishPlaying. Because then you'd have to recreate them. Just reset them so they're available for immediate playback again.

By the way, if you're going to do more with audio and want more control and better performance I highly recommend the excellent frameworks The Amazing Audio Engine 2, or AudioKit

FilipD
  • 144
  • 3
  • Thank you for the answer, FilipD. I can’t set up the players as you’ve described (this was how I tried it originally) because I need to use the playAtTime method to have the best accuracy in timing. In fact, there will be multiple sounds playing in the same time. I’ve stripped down the example to be clear about the problem. I can’t use the same player multiple times (when a sound should repeat) when setting them up with playAtTime because the player would be overridden with the next playAtTime method. ... – nontomatic Oct 09 '16 at 22:10
  • So, the only way is to initialise each player for a sound event, even when multiple players eventually play back the same sound file. Setting the players up as written in the code example will take a few seconds time because there are calculations done. During this time span every other process (like the user touching a row again) will be delayed until the setup for the players is finished. I can see print statements running the console for each player. Only once this is done the new setup will begin. ... – nontomatic Oct 09 '16 at 22:10
  • I need to break this process when the user is demanding a new setup for playback. All players that had been setup should be removed (this is the easy part), but all the others, which are continuing to be set up, should be removed too, immediately (which is the problem). Since this process happens in the main thread, I thought, maybe there is a way to identify these processes there and remove them from the queue. I don’t manage to do that. ... – nontomatic Oct 09 '16 at 22:10
  • If the user touches a row or a button or starts the setup in any other way doesn’t really matter, I thought tapping the row in a table view is the most common environment that would help focus on the problem. I hope my intentions have become clearer and would be happy to hear back from you. Had to split the answer in different messages due to its length! – nontomatic Oct 09 '16 at 22:11
  • Hi, I didn't mean using one player to play the 50 sounds consecutively. But rather have 50 of them ready to run. But maybe you could run your setup process inside an NSOperation which you add to an NSOperationqueue. This can then be done in another thread as well, not blocking the main thread. Using KVO you can check to see when it is finished. An operation like that can also be cancelled. – FilipD Oct 13 '16 at 08:42
  • However, you might consider going another way. Why not abandon setting up all players at once (even though you might end up not needing them, or having to re-setup them), which is kind of heavy going. But rather setup and immediately play your AVAudioplayer inside a timer, or using performSelector or something. Fire it every second, if the user presses the row again cancel the timer, and restart. This might be much quicker – FilipD Oct 13 '16 at 08:46