Dmitry Kuleshov
Dmitry Kuleshov

Reputation: 601

Swift timer won't work after invalidating and reinitiating

I'm using Timer() for indicating current audio position in audio player

func startTimer() {
        print("PlayerController: startTimer()")
        if itemPlayTimer != nil {
            return
        }
        itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
                                                  target: self,
                                                  selector: #selector(updateItemPlayerTimer),
                                                  userInfo: nil,
                                                  repeats: true)
    }

 @objc func updateItemPlayerTimer() {
        guard let currentTime = player?.currentTime else {
            return
        }
        updateTimeDescription?(currentTime)
    }

when user pause player app invalidating timer

func stopTimer() {
        itemPlayTimer?.invalidate()
        itemPlayTimer = nil
    }

But after calling startTimer() again selector won't call

Upvotes: 3

Views: 978

Answers (3)

Dmitry Kuleshov
Dmitry Kuleshov

Reputation: 601

The reason was that the timer starts executing in other thread, not in main thread.

Upvotes: 2

Awais Fayyaz
Awais Fayyaz

Reputation: 2415

Use the following code. I have used An AVPlayer with a sample Video to demonstrate pausing/playing with Timer. Logic is same for audio player. Just replace AVPlayer with your audioPlayer.

Key logic here is to properly manage the state of Playing/not playing and checking timer for nil etc.

As indicated in this Answer

startTimer() starts the timer only if it's nil and stopTimer() stops it only if it's not nil.

You have only to take care of stopping the timer before creating/starting a new one.

I have implemented this in a sample project and is working 100%.

  • Carefully See the function pauseTapped(_ sender: UIButton)
  • See sample gif At end of Code

import UIKit
import AVKit

class TimerVC: UIViewController {

  ///A container view for displaying the AVPlayer.
  @IBOutlet weak var playerView: UIView!
  /// A button to play and pause the video
  @IBOutlet weak var btnPause: UIButton!

  ///to maintain the status of AVPlayer playing or not
  var flagPlaying = true
  ///An AVPlayer for displaying and playing the video
  var player: AVPlayer?
  ///to show the current time to user
  @IBOutlet weak var lblCurrentTime: UILabel!

  override func viewDidLoad() {

    super.viewDidLoad()
    //add an AVPlayer with sample URL link
    addVideoPlayer( playerView: playerView)

  }
  ///Timer
  var itemPlayTimer: Timer?
  @objc func startTimer() {

    if itemPlayTimer != nil {
      return
    }
    itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
                                         target: self,
                                         selector: #selector(updateItemPlayerTimer),
                                         userInfo: nil,
                                         repeats: true)
  }

  ///update time label
  @objc func updateItemPlayerTimer() {
    guard let currentTime = player?.currentTime else {
      return
    }

    updateTimeDescription(currentTime())
  }

  func updateTimeDescription( _ currentTime :CMTime ) {
    self.lblCurrentTime.text = "\(currentTime.seconds)"
  }

  ///To Pause and play the video
  @IBAction func pauseTapped(_ sender: UIButton) {
    if flagPlaying {
      //pause
      if itemPlayTimer != nil {
        player?.pause()
        itemPlayTimer?.invalidate()
        itemPlayTimer = nil
        flagPlaying = false

        DispatchQueue.main.async {
          self.btnPause.setTitle("Play", for: .normal)
        }

      }

    }else {
      //not playing
      if itemPlayTimer == nil {
        player?.play()
        startTimer()

        flagPlaying = true
        DispatchQueue.main.async {
          self.btnPause.setTitle("Pause", for: .normal)
        }

      }

    }

  }

  private func addVideoPlayer(playerView: UIView) {

    //let playerItem = AVPlayerItem(asset: asset)
    player = AVPlayer.init(url: URL.init(string: "http://techslides.com/demos/sample-videos/small.mp4")!)


    let layer: AVPlayerLayer = AVPlayerLayer(player: player)
    layer.backgroundColor = UIColor.white.cgColor
    layer.frame = CGRect(x: 0, y: 0, width: playerView.frame.width, height: playerView.frame.height)
    layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
    playerView.layer.sublayers?.forEach({$0.removeFromSuperlayer()})
    playerView.layer.addSublayer(layer)

    flagPlaying = true
    player?.play()
    startTimer()
  }


}

Working Example

Working example

Let me know if you need any help

Upvotes: 0

Arash Etemad
Arash Etemad

Reputation: 1909

change your functions in this way:

func startTimer() {
        print("PlayerController: startTimer()")
        if itemPlayTimer == nil {
            itemPlayTimer = Timer.scheduledTimer(timeInterval: 0.001,
                                                  target: self,
                                                  selector: #selector(updateItemPlayerTimer),
                                                  userInfo: nil,
                                                  repeats: true)
        }

}

@objc func updateItemPlayerTimer() {
    guard let currentTime = player?.currentTime else {
        return
    }
    updateTimeDescription?(currentTime)
}

func stopTimer() {
    if itemPlayTimer != nil {
        itemPlayTimer?.invalidate()
        itemPlayTimer = nil
    }
}

Upvotes: 0

Related Questions