1

I am using a timer repeating every second to update the now playing currentPlaybackTime, so I am updating my label every second with the current song's progress. I used to use an NSTimer or in Swift a Timer to repeat every second, however I ran into problems when changing a song or start and stopping a song the seconds label would lag (it'd stay the same for 5 seconds, while you hear the song playing and then would update to the correct time). Then I went on to use GCD, and I had the same problem, even on a background queue. And then I went to use DispatchSourceTimer and I got the following logs:

Fired: 2018-03-18 13:22:17 +0000
Timer fired: 2018-03-18 13:22:17 +0000
Fired: 2018-03-18 13:22:18 +0000
Timer fired: 2018-03-18 13:22:18 +0000
Fired: 2018-03-18 13:22:19 +0000
Fired: 2018-03-18 13:22:20 +0000
Timer fired: 2018-03-18 13:22:20 +0000
Fired: 2018-03-18 13:22:21 +0000
Fired: 2018-03-18 13:22:22 +0000
Fired: 2018-03-18 13:22:23 +0000
Fired: 2018-03-18 13:22:24 +0000
Fired: 2018-03-18 13:22:25 +0000
Fired: 2018-03-18 13:22:26 +0000
Fired: 2018-03-18 13:22:27 +0000
Fired: 2018-03-18 13:22:28 +0000
Fired: 2018-03-18 13:22:29 +0000
Fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Timer fired: 2018-03-18 13:22:30 +0000
Fired: 2018-03-18 13:22:31 +0000
Timer fired: 2018-03-18 13:22:31 +0000
Fired: 2018-03-18 13:22:32 +0000
Timer fired: 2018-03-18 13:22:32 +0000

The one that says "Fired" is the actual even handler of the DispatchSourceTimer while the "Timer Fired" is within the handler function that updates the label and such. Clearly you can see that the timer is working fine, but it seems the main thread is heavily used.

Here is my code:

//timer is a DispatchSourceTimer, queue is a concurrent custom queue
public func startTimer() {
    timer?.cancel()
    timer = DispatchSource.makeTimerSource(queue: queue)
    timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1))
    timer?.setEventHandler { [weak self] in
        print("Fired: \(Date())")
        DispatchQueue.main.async {
            self?.notifyObservers()
        }
    }
    timer?.resume()
}

public func stopTimer() {
    timer?.cancel()
    timer = nil
}

And here is notifier observers:

private func notifyObservers() {
    for observer: TimeObserver in self.timeObservers {
        observer.timerFired()
    }
}

I maintain an observer array of objects that want to be notified of the timer firing.

I did a test and removed the for loop and just notified the first observer in the list and it did speed up things a bit, but still did not solve the problem.

if I just play the song and after a few seconds the main thread actually can handle it and I can show the time perfectly, but when doing heavy things like using the MPMediaPlayer framework to change a song or when calling play() and pause() on the MPMusicPlayerController it starts lagging.

Any help would be appreciated. Thank you.

Janosch Hübner
  • 1,584
  • 25
  • 44

1 Answers1

0

You can see this behavior if you instantiate your player like so:

let player = MPMusicPlayerController()

But you won't see this delay (or at least it's far less) if you use:

let player = MPMusicPlayerController.applicationQueuePlayer

or:

let player = MPMusicPlayerController.applicationMusicPlayer

On my iPhone X, I experience a 5-8 second blocking of the main thread with the former technique, but only a second or less with the latter two techniques.


I assume your dispatch timer was to test what was going on, but there's no point in using this dispatch timer on a background thread if all it's going to do is dispatch it back to the main queue. If you're main thread is blocked for reasons outside of your control, you don't want to backlog the main thread with a bunch of updates which couldn't have been processed in a timely manner.

I'd suggest reverting to a simple Timer.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • yeah I will revert back to a simple Timer, because that is not the issue. the issue is rather MPMusicPlayerController. The thing is, my app's content relies on the system music player, so applicationMusicPlayer is not really an option – Janosch Hübner Mar 18 '18 at 15:58
  • I do see the behavior you describe with `let player = MPMusicPlayerController.systemMusicPlayer`. If you are using the system music player, you may have to live with this momentary interruption in the UI. But hopefully someone else may have found a way around this. I was hoping that `prepareToPlay` would eliminate this delay, but it doesn't. – Rob Mar 18 '18 at 16:07
  • I wonder how Apple does it internally an why it can't fix this in a public API... Because I doubt it has to do with other stuff I do on the main thread. – Janosch Hübner Mar 18 '18 at 16:19
  • No, I'm sure it's nothing you did on the main thread. I created a primitive player, doing nothing on the main thread (other than updating `currentPlaybackTime` in a label) and I see the same behavior you describe. – Rob Mar 18 '18 at 16:36